BigW Consortium Gitlab

issuable_finder.rb 10 KB
Newer Older
1
# IssuableFinder
2 3 4 5 6 7 8 9
#
# Used to filter Issues and MergeRequests collections by set of params
#
# Arguments:
#   klass - actual class like Issue or MergeRequest
#   current_user - which user use
#   params:
#     scope: 'created-by-me' or 'assigned-to-me' or 'all'
10
#     state: 'opened' or 'closed' or 'all'
11 12
#     group_id: integer
#     project_id: integer
13
#     milestone_title: string
14 15 16 17
#     assignee_id: integer
#     search: string
#     label_name: string
#     sort: string
18
#     non_archived: boolean
19
#     iids: integer[]
20
#
21
class IssuableFinder
22
  NONE = '0'.freeze
23

24
  attr_accessor :current_user, :params
25

26
  def initialize(current_user, params = {})
27 28
    @current_user = current_user
    @params = params
29
  end
30

31
  def execute
32 33
    items = init_collection
    items = by_scope(items)
34 35 36 37
    items = by_state(items)
    items = by_group(items)
    items = by_search(items)
    items = by_assignee(items)
38
    items = by_author(items)
39
    items = by_due_date(items)
40
    items = by_non_archived(items)
41
    items = by_iids(items)
42 43 44 45 46
    items = by_milestone(items)
    items = by_label(items)

    # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
    items = by_project(items)
47
    sort(items)
48 49
  end

50 51 52 53 54 55 56 57
  def find(*params)
    execute.find(*params)
  end

  def find_by(*params)
    execute.find_by(*params)
  end

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
  # We often get counts for each state by running a query per state, and
  # counting those results. This is typically slower than running one query
  # (even if that query is slower than any of the individual state queries) and
  # grouping and counting within that query.
  #
  def count_by_state
    count_params = params.merge(state: nil, sort: nil)
    labels_count = label_names.any? ? label_names.count : 1
    finder = self.class.new(current_user, count_params)
    counts = Hash.new(0)

    # Searching by label includes a GROUP BY in the query, but ours will be last
    # because it is added last. Searching by multiple labels also includes a row
    # per issuable, so we have to count those in Ruby - which is bad, but still
    # better than performing multiple queries.
    #
    finder.execute.reorder(nil).group(:state).count.each do |key, value|
      counts[Array(key).last.to_sym] += value / labels_count
    end

    counts[:all] = counts.values.sum
    counts[:opened] += counts[:reopened]

    counts
  end

84 85 86 87
  def find_by!(*params)
    execute.find_by!(*params)
  end

88 89 90
  def group
    return @group if defined?(@group)

91
    @group =
92 93
      if params[:group_id].present?
        Group.find(params[:group_id])
94
      else
95 96 97 98
        nil
      end
  end

99 100 101 102
  def project?
    params[:project_id].present?
  end

103 104 105
  def project
    return @project if defined?(@project)

106 107
    project = Project.find(params[:project_id])
    project = nil unless Ability.allowed?(current_user, :"read_#{klass.to_ability_name}", project)
108

109
    @project = project
110 111
  end

112
  def projects(items = nil)
113 114 115 116 117 118
    return @projects = project if project?

    projects =
      if current_user && params[:authorized_only].presence && !current_user_related?
        current_user.authorized_projects
      elsif group
119
        GroupProjectsFinder.new(group: group, current_user: current_user).execute
120
      else
121
        ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
122
      end
123

124
    @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
125 126 127 128 129 130 131 132 133 134
  end

  def search
    params[:search].presence
  end

  def milestones?
    params[:milestone_title].present?
  end

Douwe Maan committed
135
  def filter_by_no_milestone?
136 137 138
    milestones? && params[:milestone_title] == Milestone::None.title
  end

139 140 141 142
  def milestones
    return @milestones if defined?(@milestones)

    @milestones =
143
      if milestones?
144
        scope = Milestone.where(project_id: projects)
145 146

        scope.where(title: params[:milestone_title])
147
      else
148
        Milestone.none
149 150 151
      end
  end

152 153 154 155
  def labels?
    params[:label_name].present?
  end

Douwe Maan committed
156
  def filter_by_no_label?
157
    labels? && params[:label_name].include?(Label::None.title)
158 159
  end

Tap committed
160 161 162
  def labels
    return @labels if defined?(@labels)

163 164
    @labels =
      if labels? && !filter_by_no_label?
165
        LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true)
166 167
      else
        Label.none
Tap committed
168 169 170
      end
  end

171
  def assignee_id?
172
    params[:assignee_id].present? && params[:assignee_id] != NONE
173 174
  end

175
  def assignee_username?
176
    params[:assignee_username].present? && params[:assignee_username] != NONE
177 178
  end

179
  def no_assignee?
180
    # Assignee_id takes precedence over assignee_username
181 182 183
    params[:assignee_id] == NONE || params[:assignee_username] == NONE
  end

184 185 186
  def assignee
    return @assignee if defined?(@assignee)

187
    @assignee =
188
      if assignee_id?
189
        User.find_by(id: params[:assignee_id])
190
      elsif assignee_username?
191
        User.find_by(username: params[:assignee_username])
192 193 194 195 196
      else
        nil
      end
  end

197
  def author_id?
198
    params[:author_id].present? && params[:author_id] != NONE
199 200
  end

201
  def author_username?
202
    params[:author_username].present? && params[:author_username] != NONE
203 204
  end

205
  def no_author?
206
    # author_id takes precedence over author_username
207 208 209
    params[:author_id] == NONE || params[:author_username] == NONE
  end

210 211 212
  def author
    return @author if defined?(@author)

213
    @author =
214 215 216
      if author_id?
        User.find_by(id: params[:author_id])
      elsif author_username?
217
        User.find_by(username: params[:author_username])
218 219 220 221 222
      else
        nil
      end
  end

223 224
  private

225
  def init_collection
226
    klass.all
227 228 229
  end

  def by_scope(items)
Douwe Maan committed
230 231
    case params[:scope]
    when 'created-by-me', 'authored'
232
      items.where(author_id: current_user.id)
Douwe Maan committed
233
    when 'assigned-to-me'
234
      items.where(assignee_id: current_user.id)
235
    else
Douwe Maan committed
236
      items
237 238 239 240
    end
  end

  def by_state(items)
241 242 243 244 245 246 247
    case params[:state].to_s
    when 'closed'
      items.closed
    when 'merged'
      items.respond_to?(:merged) ? items.merged : items.closed
    when 'opened'
      items.opened
248
    else
249
      items
250 251 252 253
    end
  end

  def by_group(items)
254
    # Selection by group is already covered by `by_project` and `projects`
255 256 257 258
    items
  end

  def by_project(items)
259
    items =
260
      if project?
261 262 263
        items.of_projects(projects(items)).references_project
      elsif projects(items)
        items.merge(projects(items).reorder(nil)).join_project
264 265 266
      else
        items.none
      end
267 268 269 270 271

    items
  end

  def by_search(items)
272 273
    search ? items.full_search(search) : items
  end
274

275 276
  def by_iids(items)
    params[:iids].present? ? items.where(iid: params[:iids]) : items
277 278 279
  end

  def sort(items)
280 281
    # Ensure we always have an explicit sort order (instead of inheriting
    # multiple orders when combining ActiveRecord::Relation objects).
282
    params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
283 284 285
  end

  def by_assignee(items)
286 287
    if assignee
      items = items.where(assignee_id: assignee.id)
288 289
    elsif no_assignee?
      items = items.where(assignee_id: nil)
290 291
    elsif assignee_id? || assignee_username? # assignee not found
      items = items.none
292 293 294 295 296
    end

    items
  end

297
  def by_author(items)
298 299
    if author
      items = items.where(author_id: author.id)
300 301
    elsif no_author?
      items = items.where(author_id: nil)
302 303
    elsif author_id? || author_username? # author not found
      items = items.none
304 305 306 307 308
    end

    items
  end

tiagonbotelho committed
309
  def filter_by_upcoming_milestone?
310
    params[:milestone_title] == Milestone::Upcoming.name
311 312
  end

313 314 315 316
  def filter_by_started_milestone?
    params[:milestone_title] == Milestone::Started.name
  end

317 318
  def by_milestone(items)
    if milestones?
Douwe Maan committed
319
      if filter_by_no_milestone?
320
        items = items.left_joins_milestones.where(milestone_id: [-1, nil])
tiagonbotelho committed
321
      elsif filter_by_upcoming_milestone?
322
        upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
323
        items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
324 325
      elsif filter_by_started_milestone?
        items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
326
      else
327
        items = items.with_milestone(params[:milestone_title])
328
        items_projects = projects(items)
329

330 331
        if items_projects
          items = items.where(milestones: { project_id: items_projects })
332 333 334 335 336 337 338
        end
      end
    end

    items
  end

339
  def by_label(items)
340
    if labels?
Douwe Maan committed
341
      if filter_by_no_label?
342
        items = items.without_label
343
      else
344
        items = items.with_label(label_names, params[:sort])
345
        items_projects = projects(items)
346

347 348
        if items_projects
          label_ids = LabelsFinder.new(current_user, project_ids: items_projects).execute(skip_authorization: true).select(:id)
349
          items = items.where(labels: { id: label_ids })
350
        end
351
      end
352 353
    end

354
    items
355
  end
356

357 358 359
  def by_due_date(items)
    if due_date?
      if filter_by_no_due_date?
360 361
        items = items.without_due_date
      elsif filter_by_overdue?
Rémy Coutable committed
362
        items = items.due_before(Date.today)
363
      elsif filter_by_due_this_week?
Rémy Coutable committed
364
        items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
365
      elsif filter_by_due_this_month?
Rémy Coutable committed
366
        items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
367 368
      end
    end
Rémy Coutable committed
369

370 371
    items
  end
372

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
  def filter_by_no_due_date?
    due_date? && params[:due_date] == Issue::NoDueDate.name
  end

  def filter_by_overdue?
    due_date? && params[:due_date] == Issue::Overdue.name
  end

  def filter_by_due_this_week?
    due_date? && params[:due_date] == Issue::DueThisWeek.name
  end

  def filter_by_due_this_month?
    due_date? && params[:due_date] == Issue::DueThisMonth.name
  end

  def due_date?
    params[:due_date].present? && klass.column_names.include?('due_date')
  end

Tap committed
393
  def label_names
Thijs Wouters committed
394 395 396 397 398
    if labels?
      params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
    else
      []
    end
Tap committed
399 400
  end

401 402 403 404
  def by_non_archived(items)
    params[:non_archived].present? ? items.non_archived : items
  end

405 406 407
  def current_user_related?
    params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
  end
408
end