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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -5
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/lib/karafka/web/config.rb +2 -0
- data/lib/karafka/web/tracking/consumers/reporter.rb +5 -3
- data/lib/karafka/web/ui/base.rb +6 -2
- data/lib/karafka/web/ui/controllers/base.rb +17 -0
- data/lib/karafka/web/ui/controllers/cluster.rb +5 -2
- data/lib/karafka/web/ui/controllers/consumers.rb +3 -1
- data/lib/karafka/web/ui/controllers/errors.rb +19 -6
- data/lib/karafka/web/ui/controllers/jobs.rb +3 -1
- data/lib/karafka/web/ui/controllers/requests/params.rb +10 -0
- data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
- data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
- data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
- data/lib/karafka/web/ui/lib/ttl_cache.rb +74 -0
- data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
- data/lib/karafka/web/ui/models/message.rb +114 -38
- data/lib/karafka/web/ui/models/status.rb +3 -1
- data/lib/karafka/web/ui/pro/app.rb +11 -3
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +3 -1
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
- data/lib/karafka/web/ui/pro/controllers/errors.rb +43 -10
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +52 -7
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
- data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
- data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
- data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +8 -2
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_partitions.erb +11 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic.erb +49 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/views/cluster/_partition.erb +1 -1
- data/lib/karafka/web/ui/views/errors/_error.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_pagination.erb +16 -12
- data/lib/karafka/web/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +18 -3
- metadata.gz.sig +0 -0
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d52e64643f448374a2f4efcb5e27089d5f36c8b8cde0eac5e2434c59b07d371c
|
4
|
+
data.tar.gz: '068a2ee3c3d82eeccdde1e001e6788c71980769c58013dd5290ad47002eac47e'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ef49085501fafc176d09c6813d3bafc7cb5d56231e2b655462ed12da02effe623a46c1934a8f17e9913dccf8da6405877ddede9a0f519b350613d0fbf4a66a1
|
7
|
+
data.tar.gz: 8251fe9ac27bab1b0992cb8f9baae68fe15100b48814e245589c6a38cc608ee5fa2d65d62faff0fdc8712dcff7685d82a0e87677d56bcc73fd35b07766f02af8
|
checksums.yaml.gz.sig
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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����rf$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
data/lib/karafka/web/config.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/karafka/web/ui/base.rb
CHANGED
@@ -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
|
72
|
-
|
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
|
-
|
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,
|
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,
|
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,
|
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
|