Welcome to mirror list, hosted at ThFree Co, Russian Federation.

updated_notes_paginator.rb « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d5c01bde6b3b9767677606ad1e9027f1551d8f84 (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
# frozen_string_literal: true

module Gitlab
  # UpdatedNotesPaginator implements a rudimentary form of keyset pagination on
  # top of a notes relation that has been initialized with a `last_fetched_at`
  # value. This class will attempt to limit the number of notes returned, and
  # specify a new value for `last_fetched_at` that will pick up where the last
  # page of notes left off.
  class UpdatedNotesPaginator
    LIMIT = 50
    MICROSECOND = 1_000_000

    attr_reader :next_fetched_at, :notes

    def initialize(relation, last_fetched_at:)
      @last_fetched_at = last_fetched_at
      @now = Time.current

      notes, more = fetch_page(relation)
      if more
        init_middle_page(notes)
      else
        init_final_page(notes)
      end
    end

    def metadata
      { last_fetched_at: next_fetched_at_microseconds, more: more }
    end

    private

    attr_reader :last_fetched_at, :more, :now

    def next_fetched_at_microseconds
      (next_fetched_at.to_i * MICROSECOND) + next_fetched_at.usec
    end

    def fetch_page(relation)
      relation = relation.order_updated_asc.with_order_id_asc
      notes = relation.limit(LIMIT + 1).to_a

      return [notes, false] unless notes.size > LIMIT

      marker = notes.pop # Remove the marker note

      # Although very unlikely, it is possible that more notes with the same
      # updated_at may exist, e.g., if created in bulk. Add them all to the page
      # if this is detected, so pagination won't get stuck indefinitely
      if notes.last.updated_at == marker.updated_at
        notes += relation
          .with_updated_at(marker.updated_at)
          .id_not_in(notes.map(&:id))
          .to_a
      end

      [notes, true]
    end

    def init_middle_page(notes)
      @more = true

      # The fetch overlap can be ignored if we're in an intermediate page.
      @next_fetched_at = notes.last.updated_at + NotesFinder::FETCH_OVERLAP
      @notes = notes
    end

    def init_final_page(notes)
      @more = false
      @next_fetched_at = now
      @notes = notes
    end
  end
end