summaryrefslogtreecommitdiff
path: root/app/services/boards/base_items_list_service.rb
blob: 2a9cbb83cc41ae60ba401eaaa38f5fd64751f4bc (plain)
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
156
157
158
# frozen_string_literal: true

module Boards
  class BaseItemsListService < Boards::BaseService
    include Gitlab::Utils::StrongMemoize
    include ActiveRecord::ConnectionAdapters::Quoting

    def execute
      items = init_collection

      order(items)
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def metadata(required_fields = [:issue_count, :total_issue_weight])
      fields = metadata_fields(required_fields)
      keys = fields.keys
      # TODO: eliminate need for SQL literal fragment
      columns = Arel.sql(fields.values_at(*keys).join(', '))
      results = item_model.where(id: collection_ids)
      results = query_additions(results, required_fields)
      results = results.select(columns)

      Hash[keys.zip(results.pluck(columns).flatten)]
    end
    # rubocop: enable CodeReuse/ActiveRecord

    private

    # override if needed
    def query_additions(items, required_fields)
      items
    end

    def collection_ids
      @collection_ids ||= init_collection.select(item_model.arel_table[:id])
    end

    def metadata_fields(required_fields)
      required_fields&.include?(:issue_count) ? { size: 'COUNT(*)' } : {}
    end

    def order(items)
      raise NotImplementedError
    end

    def finder
      raise NotImplementedError
    end

    def board
      raise NotImplementedError
    end

    def item_model
      raise NotImplementedError
    end

    # We memoize the query here since the finder methods we use are quite complex. This does not memoize the result of the query.
    # rubocop: disable CodeReuse/ActiveRecord
    def init_collection
      strong_memoize(:init_collection) do
        filter(finder.execute).reorder(nil)
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def filter(items)
      # when grouping board issues by epics (used in board swimlanes)
      # we need to get all issues in the board
      # TODO: ignore hidden columns -
      # https://gitlab.com/gitlab-org/gitlab/-/issues/233870
      return items if params[:all_lists]

      items = without_board_labels(items) unless list&.movable? || list&.closed?
      items = with_list_label(items) if list&.label?
      items
    end

    def list
      return unless params.key?(:id) || params.key?(:list)

      strong_memoize(:list) do
        id = params[:id]
        list = params[:list]

        if list.present?
          list
        elsif board.lists.loaded?
          board.lists.find { |l| l.id == id }
        else
          board.lists.find(id)
        end
      end
    end

    def filter_params
      set_parent
      set_state
      set_attempt_search_optimizations

      params
    end

    def set_parent
      if parent.is_a?(Group)
        params[:group_id] = parent.id
      else
        params[:project_id] = parent.id
      end
    end

    def set_state
      return if params[:all_lists]

      params[:state] = list && list.closed? ? 'closed' : 'opened'
    end

    def set_attempt_search_optimizations
      return unless params[:search].present?

      if board.group_board?
        params[:attempt_group_search_optimizations] = true
      else
        params[:attempt_project_search_optimizations] = true
      end
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def board_label_ids
      @board_label_ids ||= board.lists.movable.pluck(:label_id)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # rubocop: disable CodeReuse/ActiveRecord
    def without_board_labels(items)
      return items unless board_label_ids.any?

      items.where.not('EXISTS (?)', label_links(board_label_ids).limit(1))
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # rubocop: disable CodeReuse/ActiveRecord
    def label_links(label_ids)
      LabelLink
        .where(label_links: { target_type: item_model })
        .where(item_model.arel_table[:id].eq(LabelLink.arel_table[:target_id]).to_sql)
        .where(label_id: label_ids)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # rubocop: disable CodeReuse/ActiveRecord
    def with_list_label(items)
      items.where('EXISTS (?)', label_links(list.label_id).limit(1))
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end