karafka-web 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -5
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/lib/karafka/web/config.rb +2 -0
  6. data/lib/karafka/web/tracking/consumers/reporter.rb +5 -3
  7. data/lib/karafka/web/ui/base.rb +6 -2
  8. data/lib/karafka/web/ui/controllers/base.rb +17 -0
  9. data/lib/karafka/web/ui/controllers/cluster.rb +5 -2
  10. data/lib/karafka/web/ui/controllers/consumers.rb +3 -1
  11. data/lib/karafka/web/ui/controllers/errors.rb +19 -6
  12. data/lib/karafka/web/ui/controllers/jobs.rb +3 -1
  13. data/lib/karafka/web/ui/controllers/requests/params.rb +10 -0
  14. data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
  15. data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
  16. data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
  17. data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
  18. data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
  19. data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
  20. data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
  21. data/lib/karafka/web/ui/lib/ttl_cache.rb +74 -0
  22. data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
  23. data/lib/karafka/web/ui/models/message.rb +114 -38
  24. data/lib/karafka/web/ui/models/status.rb +3 -1
  25. data/lib/karafka/web/ui/pro/app.rb +11 -3
  26. data/lib/karafka/web/ui/pro/controllers/consumers.rb +3 -1
  27. data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
  28. data/lib/karafka/web/ui/pro/controllers/errors.rb +43 -10
  29. data/lib/karafka/web/ui/pro/controllers/explorer.rb +52 -7
  30. data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
  31. data/lib/karafka/web/ui/pro/views/errors/_error.erb +1 -1
  32. data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
  33. data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
  34. data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
  35. data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
  36. data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
  37. data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +1 -1
  38. data/lib/karafka/web/ui/pro/views/explorer/_message.erb +8 -2
  39. data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +1 -1
  40. data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
  41. data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
  42. data/lib/karafka/web/ui/pro/views/explorer/partition.erb +1 -1
  43. data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
  44. data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
  45. data/lib/karafka/web/ui/pro/views/explorer/topic/_partitions.erb +11 -0
  46. data/lib/karafka/web/ui/pro/views/explorer/topic.erb +49 -0
  47. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
  48. data/lib/karafka/web/ui/views/cluster/_partition.erb +1 -1
  49. data/lib/karafka/web/ui/views/errors/_error.erb +1 -1
  50. data/lib/karafka/web/ui/views/shared/_pagination.erb +16 -12
  51. data/lib/karafka/web/version.rb +1 -1
  52. data.tar.gz.sig +0 -0
  53. metadata +18 -3
  54. metadata.gz.sig +0 -0
  55. data/lib/karafka/web/ui/lib/paginate_array.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25d9203285a7e2587ec78b57bcde9951c647e5eb026c4c32cd7040bfb844ae88
4
- data.tar.gz: 46b15b79ebd7b6ce8d9a234c395a6f71bfb7891b8a9cb00d4121fd055bf3f727
3
+ metadata.gz: d52e64643f448374a2f4efcb5e27089d5f36c8b8cde0eac5e2434c59b07d371c
4
+ data.tar.gz: '068a2ee3c3d82eeccdde1e001e6788c71980769c58013dd5290ad47002eac47e'
5
5
  SHA512:
6
- metadata.gz: 529d6eff4eeae68edadd3989cf4e5de9c1c91ea5e0612e2df976717a8700bbbc53e4d917d0421a8e930392b973f19d8a31366be036955392f8ac9a89b821be69
7
- data.tar.gz: bcd6de8973599bb034733dd2921cff7277f11aab5e735917695e87722049fcf8bf3a4bcdb6e52a86cbcc4c0dfa91d2ebe7f90c9cc9af3f484ae1b0c6a8dbe8a3
6
+ metadata.gz: 0ef49085501fafc176d09c6813d3bafc7cb5d56231e2b655462ed12da02effe623a46c1934a8f17e9913dccf8da6405877ddede9a0f519b350613d0fbf4a66a1
7
+ data.tar.gz: 8251fe9ac27bab1b0992cb8f9baae68fe15100b48814e245589c6a38cc608ee5fa2d65d62faff0fdc8712dcff7685d82a0e87677d56bcc73fd35b07766f02af8
checksums.yaml.gz.sig CHANGED
@@ -1,5 +1,3 @@
1
-
2
- 7yʙ|����
3
- e
4
- լ�Q:.�^j!�yO5�plZȼ�����Β�>,�5SV����2x�Kw������zq2�m67Ty�M��ٞT��[G��0� )�
5
- Ͱ� (}���%M��Q�ޯ��؏�Wu�\����P��$3a\{D�!!s��F5�D���,k���
1
+ ��lOo�ė����ɝ��"Z�`n�mi��:�Ѿ��9)E���6��S� ��� ���I�M<ѓ��(gR:N|Z�C�bd�Z��a�D��wȁƑT�*^� K�$"�
2
+ �UA��i�t�����)�̞L_=^2�n@�M>�W�ĞUm�w����1�|D+!��b�X��r��=�Vn����
3
+ ������?A��+uv����r f$T �U!�t~� �o[�7гS�?��F��_u
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Karafka Web changelog
2
2
 
3
+ ## 0.7.0 (Unreleased)
4
+ - **[Feature]** Introduce per-topic data exploration in the Explorer.
5
+ - [Improvement] Introduce in-memory cluster state cached to improve performance.
6
+ - [Improvement] Switch to offset based pagination instead of per-page pagination.
7
+ - [Improvement] Avoid double-reading of watermark offsets for explorer and errors display.
8
+ - [Improvement] When no params needed for a page, do not include empty params.
9
+ - [Improvement] Do not include page when page is 1 in the url.
10
+ - [Refactor] Reorganize pagination engine to support offset based pagination.
11
+
12
+ ## 0.6.2 (2023-07-22)
13
+ - [Fix] Fix extensive CPU usage when using HPET clock instead of TSC due to interrupt frequency.
14
+
3
15
  ## 0.6.1 (2023-06-25)
4
16
  - [Improvement] Include the karafka-web version in the status page tags.
5
17
  - [Improvement] Report `karafka-web` version that is running in particular processes.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka-web (0.6.1)
4
+ karafka-web (0.6.2)
5
5
  erubi (~> 1.4)
6
6
  karafka (>= 2.1.4, < 3.0.0)
7
7
  karafka-core (>= 2.0.13, < 3.0.0)
@@ -80,6 +80,8 @@ module Karafka
80
80
  end
81
81
 
82
82
  setting :ui do
83
+ setting :cache, default: Ui::Lib::TtlCache.new(60_000 * 5)
84
+
83
85
  # Should the payload be decrypted for the Pro Web UI. Default to `false` due to security
84
86
  # reasons
85
87
  setting :decrypt, default: false
@@ -93,12 +93,14 @@ module Karafka
93
93
  def call
94
94
  @running = true
95
95
 
96
+ # We won't track more often anyhow but want to try frequently not to miss a window
97
+ # We need to convert the sleep interval into seconds for sleep
98
+ sleep_time = ::Karafka::Web.config.tracking.interval.to_f / 1_000 / 10
99
+
96
100
  loop do
97
101
  report
98
102
 
99
- # We won't track more often anyhow but want to try frequently not to miss a window
100
- # We need to convert the sleep interval into seconds for sleep
101
- sleep(::Karafka::Web.config.tracking.interval / 1_000 / 10)
103
+ sleep(sleep_time)
102
104
  end
103
105
  end
104
106
 
@@ -68,8 +68,12 @@ module Karafka
68
68
  # Allows us to build current path with additional params
69
69
  # @param query_data [Hash] query params we want to add to the current path
70
70
  path :current do |query_data = {}|
71
- q = query_data.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
72
- "#{request.path}?#{q}"
71
+ q = query_data
72
+ .select { |_, v| v }
73
+ .map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }
74
+ .join('&')
75
+
76
+ [request.path, q].compact.delete_if(&:empty?).join('?')
73
77
  end
74
78
 
75
79
  # Sets appropriate template variables based on the response object and renders the
@@ -12,6 +12,8 @@ module Karafka
12
12
  @params = params
13
13
  end
14
14
 
15
+ private
16
+
15
17
  # Builds the respond data object with assigned attributes based on instance variables.
16
18
  #
17
19
  # @return [Responses::Data] data that should be used to render appropriate view
@@ -33,6 +35,21 @@ module Karafka
33
35
  attributes
34
36
  )
35
37
  end
38
+
39
+ # Initializes the expected pagination engine and assigns expected arguments
40
+ # @param args Any arguments accepted by the selected pagination engine
41
+ def paginate(*args)
42
+ engine = case args.count
43
+ when 2
44
+ Ui::Lib::Paginations::PageBased
45
+ when 4
46
+ Ui::Lib::Paginations::OffsetBased
47
+ else
48
+ raise ::Karafka::Errors::UnsupportedCaseError, args.count
49
+ end
50
+
51
+ @pagination = engine.new(*args)
52
+ end
36
53
  end
37
54
  end
38
55
  end
@@ -8,7 +8,8 @@ module Karafka
8
8
  class Cluster < Base
9
9
  # List cluster info data
10
10
  def index
11
- @cluster_info = Karafka::Admin.cluster_info
11
+ # Make sure, that for the cluster view we always get the most recent cluster state
12
+ @cluster_info = Models::ClusterInfo.fetch(cached: false)
12
13
 
13
14
  partitions_total = []
14
15
 
@@ -18,11 +19,13 @@ module Karafka
18
19
  end
19
20
  end
20
21
 
21
- @partitions, @next_page = Ui::Lib::PaginateArray.new.call(
22
+ @partitions, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
22
23
  partitions_total,
23
24
  @params.current_page
24
25
  )
25
26
 
27
+ paginate(@params.current_page, !last_page)
28
+
26
29
  respond
27
30
  end
28
31
 
@@ -11,11 +11,13 @@ module Karafka
11
11
  def index
12
12
  @current_state = Models::State.current!
13
13
  @counters = Models::Counters.new(@current_state)
14
- @processes, @next_page = Lib::PaginateArray.new.call(
14
+ @processes, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
15
15
  Models::Processes.active(@current_state),
16
16
  @params.current_page
17
17
  )
18
18
 
19
+ paginate(@params.current_page, !last_page)
20
+
19
21
  respond
20
22
  end
21
23
  end
@@ -10,13 +10,15 @@ module Karafka
10
10
  class Errors < Base
11
11
  # Lists first page of the errors
12
12
  def index
13
- @previous_page, @error_messages, @next_page, = Models::Message.page(
14
- errors_topic,
15
- 0,
16
- @params.current_page
17
- )
18
-
19
13
  @watermark_offsets = Ui::Models::WatermarkOffsets.find(errors_topic, 0)
14
+ previous_offset, @error_messages, next_offset, = current_page_data
15
+
16
+ paginate(
17
+ previous_offset,
18
+ @params.current_offset,
19
+ next_offset,
20
+ @error_messages.map(&:offset)
21
+ )
20
22
 
21
23
  respond
22
24
  end
@@ -34,6 +36,17 @@ module Karafka
34
36
 
35
37
  private
36
38
 
39
+ # @return [Array] Array with requested messages as well as pagination details and other
40
+ # obtained metadata
41
+ def current_page_data
42
+ Models::Message.offset_page(
43
+ errors_topic,
44
+ 0,
45
+ @params.current_offset,
46
+ @watermark_offsets
47
+ )
48
+ end
49
+
37
50
  # @return [String] errors topic
38
51
  def errors_topic
39
52
  ::Karafka::Web.config.topics.errors
@@ -19,11 +19,13 @@ module Karafka
19
19
  end
20
20
  end
21
21
 
22
- @jobs, @next_page = Ui::Lib::PaginateArray.new.call(
22
+ @jobs, last_page = Ui::Lib::Paginations::Paginators::Arrays.call(
23
23
  jobs_total,
24
24
  @params.current_page
25
25
  )
26
26
 
27
+ paginate(@params.current_page, !last_page)
28
+
27
29
  respond
28
30
  end
29
31
  end
@@ -22,6 +22,16 @@ module Karafka
22
22
  page.positive? ? page : 1
23
23
  end
24
24
  end
25
+
26
+ # @return [Integer] offset from which we want to start. `-1` indicates, that we want
27
+ # to show the first page discovered based on the high watermark offset. If no offset
28
+ # is provided, we go with the high offset first page approach
29
+ def current_offset
30
+ @current_offset ||= begin
31
+ offset = @request_params.fetch('offset', -1).to_i
32
+ offset < -1 ? -1 : offset
33
+ end
34
+ end
25
35
  end
26
36
  end
27
37
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ # Namespace for all the types of pagination engines we want to support
8
+ module Paginations
9
+ # Abstraction on top of pagination, so we can alter pagination key and other things
10
+ # for non-standard pagination views (non page based, etc)
11
+ #
12
+ # @note We do not use `_page` explicitly to indicate, that the page scope may not operate
13
+ # on numerable pages (1,2,3,4) but can operate on offsets or times, etc. `_offset` is
14
+ # more general and may refer to many types of pagination.
15
+ class Base
16
+ attr_reader :previous_offset, :current_offset, :next_offset
17
+
18
+ # @return [Boolean] Should we show pagination at all
19
+ def paginate?
20
+ raise NotImplementedError, 'Implement in a subclass'
21
+ end
22
+
23
+ # @return [Boolean] Should first offset link be active. If false, the first offset link
24
+ # will be disabled
25
+ def first_offset?
26
+ raise NotImplementedError, 'Implement in a subclass'
27
+ end
28
+
29
+ # @return [String] first offset url value
30
+ def first_offset
31
+ raise NotImplementedError, 'Implement in a subclass'
32
+ end
33
+
34
+ # @return [Boolean] Should previous offset link be active. If false, the previous
35
+ # offset link will be disabled
36
+ def previous_offset?
37
+ raise NotImplementedError, 'Implement in a subclass'
38
+ end
39
+
40
+ # @return [Boolean] Should we show current offset. If false, the current offset link
41
+ # will not be visible at all. Useful for non-linear pagination.
42
+ def current_offset?
43
+ raise NotImplementedError, 'Implement in a subclass'
44
+ end
45
+
46
+ # @return [Boolean] Should we show next offset pagination. If false, next offset link
47
+ # will be disabled.
48
+ def next_offset?
49
+ raise NotImplementedError, 'Implement in a subclass'
50
+ end
51
+
52
+ # @return [String] the url offset key
53
+ def offset_key
54
+ raise NotImplementedError, 'Implement in a subclass'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ # Kafka offset based pagination backend
9
+ #
10
+ # Allows us to support paginating over offsets
11
+ class OffsetBased < Base
12
+ # @param previous_offset [Integer, false] previous offset or false if should not be
13
+ # presented
14
+ # @param current_offset [Integer] current offset
15
+ # @param next_offset [Integer, Boolean] should we show next offset page button. If
16
+ # false it will not be presented.
17
+ # @param visible_offsets [Array<Integer>] offsets that are visible in the paginated
18
+ # view. It is needed for the current page label
19
+ def initialize(
20
+ previous_offset,
21
+ current_offset,
22
+ next_offset,
23
+ visible_offsets
24
+ )
25
+ @previous_offset = previous_offset
26
+ @current_offset = current_offset
27
+ @next_offset = next_offset
28
+ @visible_offsets = visible_offsets
29
+ super()
30
+ end
31
+
32
+ # Show pagination only when there is more than one page of results to be presented
33
+ #
34
+ # @return [Boolean]
35
+ def paginate?
36
+ @current_offset && (!!@previous_offset || !!@next_offset)
37
+ end
38
+
39
+ # @return [Boolean] active only when we are not on the first page. First page is always
40
+ # indicated by the current offset being -1. If there is someone that sets up the
41
+ # current offset to a value equal to the last message in the topic partition, we do
42
+ # not consider it as a first page and we allow to "reset" to -1 via the first page
43
+ # button
44
+ def first_offset?
45
+ @current_offset != -1
46
+ end
47
+
48
+ # @return [Boolean] first page offset is always nothing because we use the default -1
49
+ # for the offset.
50
+ def first_offset
51
+ false
52
+ end
53
+
54
+ # @return [Boolean] Active previous page link when it is not the first page
55
+ def previous_offset?
56
+ !!@previous_offset
57
+ end
58
+
59
+ # @return [Boolean] We show current label with offsets that are present on the given
60
+ # page
61
+ def current_offset?
62
+ true
63
+ end
64
+
65
+ # @return [Boolean] move to the next page if not false. False indicates, that there is
66
+ # no next page to move to
67
+ def next_offset?
68
+ !!@next_offset
69
+ end
70
+
71
+ # If there is no next offset, we point to 0 as there should be no smaller offset than
72
+ # that in Kafka ever
73
+ # @return [Integer]
74
+ def next_offset
75
+ next_offset? ? @next_offset : 0
76
+ end
77
+
78
+ # @return [String] label of the current page. It is combined out of the first and
79
+ # the last offsets to show the range where we are. It will be empty if no offsets
80
+ # but this is not a problem as then we should not display pagination at all
81
+ def current_label
82
+ first = @visible_offsets.first
83
+ last = @visible_offsets.last
84
+ [first, last].compact.uniq.join(' - ').to_s
85
+ end
86
+
87
+ # @return [String] for offset based pagination we use the offset param name
88
+ def offset_key
89
+ 'offset'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ # Regular page-based pagination engine
9
+ class PageBased < Base
10
+ # @param current_offset [Integer] current page
11
+ # @param show_next_offset [Boolean] should we show next page
12
+ # (value is computed automatically)
13
+ def initialize(
14
+ current_offset,
15
+ show_next_offset
16
+ )
17
+ @previous_offset = current_offset - 1
18
+ @current_offset = current_offset
19
+ @next_offset = show_next_offset ? current_offset + 1 : false
20
+ super()
21
+ end
22
+
23
+ # Show pagination only when there is more than one page
24
+ # @return [Boolean]
25
+ def paginate?
26
+ @current_offset && (@current_offset > 1 || !!@next_offset)
27
+ end
28
+
29
+ # @return [Boolean] active the first page link when we are not on the first page
30
+ def first_offset?
31
+ @current_offset > 1
32
+ end
33
+
34
+ # @return [Boolean] first page for page based pagination is always empty as it moves us
35
+ # to the initial page so we do not include any page info
36
+ def first_offset
37
+ false
38
+ end
39
+
40
+ # @return [Boolean] Active previous page link when it is not the first page
41
+ def previous_offset?
42
+ @current_offset > 1
43
+ end
44
+
45
+ # @return [Boolean] always show current offset pagination value
46
+ def current_offset?
47
+ true
48
+ end
49
+
50
+ # @return [String] label of the current page
51
+ def current_label
52
+ @current_offset.to_s
53
+ end
54
+
55
+ # @return [Boolean] move to the next page if not false. False indicates, that there is
56
+ # no next page to move to
57
+ def next_offset?
58
+ @next_offset
59
+ end
60
+
61
+ # @return [String] for page pages pagination, always use page as the url value
62
+ def offset_key
63
+ 'page'
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ # Namespace for commands that build paginated resources based on the provided page
9
+ module Paginators
10
+ # A simple wrapper for paginating array related data structures
11
+ # We call this with plural (same with `Sets`) to avoid confusion with Ruby classes
12
+ class Arrays < Base
13
+ class << self
14
+ # @param array [Array] array we want to paginate
15
+ # @param current_page [Integer] page we want to be on
16
+ # @return [Array<Array, Boolean>] Array with two elements: first is the array with
17
+ # data of the given page and second is a boolean flag with info if the elements we got
18
+ # are from the last page
19
+ def call(array, current_page)
20
+ slices = array.each_slice(per_page).to_a
21
+ current_data = slices[current_page - 1] || []
22
+ last_page = !(slices.count >= current_page - 1 && current_data.size >= per_page)
23
+
24
+ [current_data, last_page]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ module Paginators
9
+ # Base paginator
10
+ class Base
11
+ class << self
12
+ # @return [Integer] number of elements per page
13
+ def per_page
14
+ ::Karafka::Web.config.ui.per_page
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ module Paginators
9
+ # Paginator for selecting proper range of partitions for each page
10
+ # For topics with a lot of partitions we cannot get all the data efficiently, that
11
+ # is why we limit number of partitions per page and reduce the operations
12
+ # that way. This allows us to effectively display more while not having to fetch
13
+ # more partitions then the number of messages per page.
14
+ # In cases like this we distribute partitions evenly part of partitions on each of
15
+ # the pages. This may become unreliable for partitions that are not evenly
16
+ # distributed but this allows us to display data for as many partitions as we want
17
+ # without overloading the system
18
+ class Partitions < Base
19
+ class << self
20
+ # Computers the partitions slice, materialized page and the limitations status
21
+ # for a given page
22
+ # @param partitions_count [Integer] number of partitions for a given topic
23
+ # @param current_page [Integer] current page
24
+ # @return [Array<Array<Integer>, Integer, Boolean>] list of partitions that should
25
+ # be active on a given page, materialized page for them and info if we had to
26
+ # limit the partitions number on a given page
27
+ def call(partitions_count, current_page)
28
+ # How many "chunks" of partitions we will have
29
+ slices_count = (partitions_count / per_page.to_f).ceil
30
+ # How many partitions in a single slice should we have
31
+ in_slice = (partitions_count / slices_count.to_f).ceil
32
+ # Which "chunked" page do we want to get
33
+ materialized_page = (current_page / slices_count.to_f).ceil
34
+ # Which slice is the one we are operating on
35
+ active_slice_index = (current_page - 1) % slices_count
36
+ # All available slices so we can pick one that is active
37
+ partitions_slices = (0...partitions_count).each_slice(in_slice).to_a
38
+ # Select active partitions only
39
+ active_partitions = partitions_slices[active_slice_index]
40
+ # Are we limiting ourselves because of partition count
41
+ limited = slices_count > 1
42
+
43
+ [active_partitions, materialized_page, limited]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Ui
6
+ module Lib
7
+ module Paginations
8
+ module Paginators
9
+ # Paginator that allows us to take several lists/sets and iterate over them in a
10
+ # round-robin fashion.
11
+ #
12
+ # It does not have to iterate over all the elements from each set for higher pages
13
+ # making it much more effective than the naive implementation.
14
+ class Sets < Base
15
+ class << self
16
+ # @param counts [Array<Integer>] sets elements counts
17
+ # @param current_page [Integer] page number
18
+ # @return [Hash<Integer, Range>] hash with integer keys indicating the count
19
+ # location and the range needed to be taken of elements (counting backwards) for
20
+ # each partition
21
+ def call(counts, current_page)
22
+ return {} if current_page < 1
23
+
24
+ lists = counts.dup.map.with_index { |el, i| [i, el] }
25
+
26
+ curr_item_index = 0
27
+ curr_list_index = 0
28
+ items_to_skip_count = per_page * (current_page - 1)
29
+
30
+ loop do
31
+ lists_count = lists.length
32
+ return {} if lists_count.zero?
33
+
34
+ shortest_list_count = lists.map(&:last).min
35
+ mover = (shortest_list_count - curr_item_index)
36
+ items_we_are_considering_count = lists_count * mover
37
+
38
+ if items_we_are_considering_count >= items_to_skip_count
39
+ curr_item_index += items_to_skip_count / lists_count
40
+ curr_list_index = items_to_skip_count % lists_count
41
+ break
42
+ else
43
+ curr_item_index = shortest_list_count
44
+ lists.delete_if { |x| x.last == shortest_list_count }
45
+ items_to_skip_count -= items_we_are_considering_count
46
+ end
47
+ end
48
+
49
+ page_items = []
50
+ largest_list_count = lists.map(&:last).max
51
+
52
+ while page_items.length < per_page && curr_item_index < largest_list_count
53
+ curr_list = lists[curr_list_index]
54
+
55
+ if curr_item_index < curr_list.last
56
+ page_items << [curr_list.first, curr_item_index]
57
+ end
58
+
59
+ curr_list_index += 1
60
+ if curr_list_index == lists.length
61
+ curr_list_index = 0
62
+ curr_item_index += 1
63
+ end
64
+ end
65
+
66
+ hashed = Hash.new { |h, k| h[k] = [] }
67
+
68
+ page_items.each do |el|
69
+ hashed[el.first] << el.last
70
+ end
71
+
72
+ hashed.each do |key, value|
73
+ hashed[key] = (value.first..value.last)
74
+ end
75
+
76
+ hashed
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end