summaryrefslogtreecommitdiff
path: root/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb
blob: 5ff7c88970ca79dee13a66f461840bef66d89db9 (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
# frozen_string_literal: true

module Gitlab
  module Diff
    module FileCollection
      # Builds a paginated diff file collection and collects pagination
      # metadata.
      #
      # It doesn't handle caching yet as we're not prepared to write/read
      # separate file keys (https://gitlab.com/gitlab-org/gitlab/issues/30550).
      #
      class MergeRequestDiffBatch < MergeRequestDiffBase
        DEFAULT_BATCH_PAGE = 1
        DEFAULT_BATCH_SIZE = 30

        attr_reader :pagination_data

        def initialize(merge_request_diff, batch_page, batch_size, diff_options:)
          super(merge_request_diff, diff_options: diff_options)

          @paginated_collection = load_paginated_collection(batch_page, batch_size, diff_options)

          @pagination_data = {
            current_page: current_page,
            next_page: next_page,
            total_pages: total_pages
          }
        end

        override :diffs
        def diffs
          strong_memoize(:diffs) do
            @merge_request_diff.opening_external_diff do
              # Avoiding any extra queries.
              collection = @paginated_collection.to_a

              # The offset collection and calculation is required so that we
              # know how much has been loaded in previous batches, collapsing
              # the current paginated set accordingly (collection limit calculation).
              # See: https://docs.gitlab.com/ee/development/diffs.html#diff-collection-limits
              #
              offset_index = collection.first&.index
              options = diff_options.dup

              collection =
                if offset_index && offset_index > 0
                  offset_collection = relation.limit(offset_index) # rubocop:disable CodeReuse/ActiveRecord
                  options[:offset_index] = offset_index
                  offset_collection + collection
                else
                  collection
                end

              Gitlab::Git::DiffCollection.new(collection.map(&:to_hash), options)
            end
          end
        end

        private

        def relation
          @merge_request_diff.merge_request_diff_files
        end

        def current_page
          return if @paginated_collection.blank?

          batch_gradual_load? ? nil : @paginated_collection.current_page
        end

        def next_page
          return if @paginated_collection.blank?

          batch_gradual_load? ? nil : @paginated_collection.next_page
        end

        def total_pages
          return if @paginated_collection.blank?

          batch_gradual_load? ? relation.size : @paginated_collection.total_pages
        end

        # rubocop: disable CodeReuse/ActiveRecord
        def load_paginated_collection(batch_page, batch_size, diff_options)
          batch_page ||= DEFAULT_BATCH_PAGE
          batch_size ||= DEFAULT_BATCH_SIZE

          paths = diff_options&.fetch(:paths, nil)

          paginated_collection = if batch_gradual_load?
                                   relation.offset(batch_page).limit([batch_size.to_i, DEFAULT_BATCH_SIZE].min)
                                 else
                                   relation.page(batch_page).per(batch_size)
                                 end

          paginated_collection = paginated_collection.by_paths(paths) if paths

          paginated_collection
        end
        # rubocop: enable CodeReuse/ActiveRecord

        def batch_gradual_load?
          Feature.enabled?(:diffs_gradual_load, @merge_request_diff.project, default_enabled: true)
        end
      end
    end
  end
end