1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
# frozen_string_literal: true
module Issuables
class LabelFilter < BaseFilter
include Gitlab::Utils::StrongMemoize
extend Gitlab::Cache::RequestCache
def initialize(project:, group:, **kwargs)
@project = project
@group = group
super(**kwargs)
end
def filter(issuables)
filtered = by_label(issuables)
by_negated_label(filtered)
end
def label_names_excluded_from_priority_sort
label_names_from_params
end
private
# rubocop: disable CodeReuse/ActiveRecord
def by_label(issuables)
return issuables unless label_names_from_params.present?
target_model = issuables.model
if filter_by_no_label?
issuables.where(label_link_query(target_model).arel.exists.not)
elsif filter_by_any_label?
issuables.where(label_link_query(target_model).arel.exists)
else
issuables_with_selected_labels(issuables, label_names_from_params)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def by_negated_label(issuables)
return issuables unless label_names_from_not_params.present?
issuables_without_selected_labels(issuables, label_names_from_not_params)
end
def filter_by_no_label?
label_names_from_params.map(&:downcase).include?(FILTER_NONE)
end
def filter_by_any_label?
label_names_from_params.map(&:downcase).include?(FILTER_ANY)
end
# rubocop: disable CodeReuse/ActiveRecord
def issuables_with_selected_labels(issuables, label_names)
target_model = issuables.model
if root_namespace
all_label_ids = find_label_ids(label_names)
# Found less labels in the DB than we were searching for. Return nothing.
return issuables.none if all_label_ids.size != label_names.size
all_label_ids.each do |label_ids|
issuables = issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists)
end
else
label_names.each do |label_name|
issuables = issuables.where(label_link_query(target_model, label_names: label_name).arel.exists)
end
end
issuables
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def issuables_without_selected_labels(issuables, label_names)
target_model = issuables.model
if root_namespace
label_ids = find_label_ids(label_names).flatten(1)
issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists.not)
else
issuables.where(label_link_query(target_model, label_names: label_names).arel.exists.not)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def find_label_ids(label_names)
group_labels = Label
.where(project_id: nil)
.where(title: label_names)
.where(group_id: root_namespace.self_and_descendant_ids)
project_labels = Label
.where(group_id: nil)
.where(title: label_names)
.where(project_id: Project.select(:id).where(namespace_id: root_namespace.self_and_descendant_ids))
Label
.from_union([group_labels, project_labels], remove_duplicates: false)
.reorder(nil)
.pluck(:title, :id)
.group_by(&:first)
.values
.map { |labels| labels.map(&:last) }
end
# Avoid repeating label queries times when the finder is instantiated multiple times during the request.
request_cache(:find_label_ids) { root_namespace.id }
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def label_link_query(target_model, label_ids: nil, label_names: nil)
relation = LabelLink
.where(target_type: target_model.name)
.where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
relation = relation.where(label_id: label_ids) if label_ids
relation = relation.joins(:label).where(labels: { name: label_names }) if label_names
relation
end
# rubocop: enable CodeReuse/ActiveRecord
def label_names_from_params
return if params[:label_name].blank?
strong_memoize(:label_names_from_params) do
split_label_names(params[:label_name])
end
end
def label_names_from_not_params
return if not_params.blank? || not_params[:label_name].blank?
strong_memoize(:label_names_from_not_params) do
split_label_names(not_params[:label_name])
end
end
def split_label_names(label_name_param)
label_name_param.is_a?(String) ? label_name_param.split(',') : label_name_param
end
def root_namespace
strong_memoize(:root_namespace) do
(@project || @group)&.root_ancestor
end
end
end
end
|