summaryrefslogtreecommitdiff
path: root/app/services/boards/issues/list_service.rb
blob: ab9d11abe98ca8648a31e21aa36369ba8996b8f9 (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
  module Issues
    class ListService < Boards::BaseService
      include Gitlab::Utils::StrongMemoize

      def self.valid_params
        IssuesFinder.valid_params
      end

      def execute
        return fetch_issues.order_closed_date_desc if list&.closed?

        fetch_issues.order_by_position_and_priority(with_cte: params[:search].present?)
      end

      # rubocop: disable CodeReuse/ActiveRecord
      def metadata
        issues = Issue.arel_table
        keys = metadata_fields.keys
        # TODO: eliminate need for SQL literal fragment
        columns = Arel.sql(metadata_fields.values_at(*keys).join(', '))
        results = Issue.where(id: fetch_issues.select(issues[:id])).pluck(columns)

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

      private

      def metadata_fields
        { size: 'COUNT(*)' }
      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 fetch_issues
        strong_memoize(:fetch_issues) do
          issues = IssuesFinder.new(current_user, filter_params).execute

          filter(issues).reorder(nil)
        end
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def filter(issues)
        # 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 issues if params[:all_lists]

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

      def board
        @board ||= parent.boards.find(params[:board_id])
      end

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

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

          if 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_scope
        set_non_archived
        set_attempt_search_optimizations
        set_issue_types

        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_scope
        params[:include_subgroups] = board.group_board?
      end

      def set_non_archived
        params[:non_archived] = parent.is_a?(Group)
      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

      def set_issue_types
        params[:issue_types] = Issue::TYPES_FOR_LIST
      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(issues)
        return issues unless board_label_ids.any?

        issues.where.not('EXISTS (?)', issues_label_links.limit(1))
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # rubocop: disable CodeReuse/ActiveRecord
      def issues_label_links
        LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id").where(label_id: board_label_ids)
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # rubocop: disable CodeReuse/ActiveRecord
      def with_list_label(issues)
        issues.where('EXISTS (?)', LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id")
                                            .where("label_links.label_id = ?", list.label_id).limit(1))
      end
      # rubocop: enable CodeReuse/ActiveRecord

      def board_group
        board.group_board? ? parent : parent.group
      end
    end
  end
end

Boards::Issues::ListService.prepend_if_ee('EE::Boards::Issues::ListService')