pg_eventstore 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/pg_eventstore/extensions/callback_handlers_extension.rb +21 -0
  4. data/lib/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rb +38 -0
  5. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +45 -0
  6. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +103 -0
  7. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +75 -0
  8. data/lib/pg_eventstore/subscriptions/commands_handler.rb +13 -29
  9. data/lib/pg_eventstore/subscriptions/events_processor.rb +17 -41
  10. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +77 -125
  11. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +30 -55
  12. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +70 -0
  13. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +8 -2
  14. data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +39 -0
  15. data/lib/pg_eventstore/utils.rb +8 -0
  16. data/lib/pg_eventstore/version.rb +1 -1
  17. data/lib/pg_eventstore/web/application.rb +9 -1
  18. data/lib/pg_eventstore/web/paginator/events_collection.rb +1 -3
  19. data/lib/pg_eventstore/web/paginator/helpers.rb +4 -1
  20. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +10 -1
  21. data/lib/pg_eventstore/web/subscriptions/helpers.rb +3 -4
  22. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -0
  23. data/lib/pg_eventstore/web/views/home/partials/events.erb +6 -1
  24. data/lib/pg_eventstore/web/views/subscriptions/index.erb +2 -2
  25. data/lib/pg_eventstore.rb +1 -0
  26. data/sig/interfaces/_raw_event_handler.rbs +3 -0
  27. data/sig/pg_eventstore/extensions/callback_handlers_extension.rbs +11 -0
  28. data/sig/pg_eventstore/subscriptions/callback_handlers/commands_handler_handlers.rbs +10 -0
  29. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +11 -0
  30. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +31 -0
  31. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +19 -0
  32. data/sig/pg_eventstore/subscriptions/events_processor.rbs +1 -1
  33. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +2 -30
  34. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +27 -0
  35. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +1 -1
  36. data/sig/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rbs +24 -0
  37. data/sig/pg_eventstore/utils.rbs +2 -0
  38. data/sig/pg_eventstore/web/subscriptions/helpers.rbs +1 -1
  39. metadata +17 -2
@@ -6,11 +6,8 @@ module PgEventstore
6
6
  class SubscriptionFeeder
7
7
  extend Forwardable
8
8
 
9
- # @return [Integer] number of seconds between heartbeat updates
10
- HEARTBEAT_INTERVAL = 10 # seconds
11
-
12
- def_delegators :subscriptions_set, :id
13
9
  def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async
10
+ def_delegators :@subscriptions_lifecycle, :force_lock!
14
11
 
15
12
  # @param config_name [Symbol]
16
13
  # @param set_name [String]
@@ -18,23 +15,27 @@ module PgEventstore
18
15
  # @param retries_interval [Integer] a delay between retries of failed SubscriptionsSet
19
16
  def initialize(config_name:, set_name:, max_retries:, retries_interval:)
20
17
  @config_name = config_name
21
- @runners = []
22
- @subscriptions_set_attrs = {
23
- name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval
24
- }
25
- @commands_handler = CommandsHandler.new(@config_name, self, @runners)
26
18
  @basic_runner = BasicRunner.new(0.2, 0)
27
- @force_lock = false
28
- @subscriptions_pinged_at = Time.at(0)
19
+ @subscriptions_set_lifecycle = SubscriptionsSetLifecycle.new(
20
+ @config_name,
21
+ { name: set_name, max_restarts_number: max_retries, time_between_restarts: retries_interval }
22
+ )
23
+ @subscriptions_lifecycle = SubscriptionsLifecycle.new(@config_name, @subscriptions_set_lifecycle)
24
+ @commands_handler = CommandsHandler.new(@config_name, self, @subscriptions_lifecycle.runners)
29
25
  attach_runner_callbacks
30
26
  end
31
27
 
28
+ # @return [Integer]
29
+ def id
30
+ @subscriptions_set_lifecycle.persisted_subscriptions_set.id
31
+ end
32
+
32
33
  # Adds SubscriptionRunner to the set
33
34
  # @param runner [PgEventstore::SubscriptionRunner]
34
35
  # @return [PgEventstore::SubscriptionRunner]
35
36
  def add(runner)
36
37
  assert_proper_state!
37
- @runners.push(runner)
38
+ @subscriptions_lifecycle.runners.push(runner)
38
39
  runner
39
40
  end
40
41
 
@@ -43,7 +44,7 @@ module PgEventstore
43
44
  def start_all
44
45
  return self unless @basic_runner.running?
45
46
 
46
- @runners.each(&:start)
47
+ @subscriptions_lifecycle.runners.each(&:start)
47
48
  self
48
49
  end
49
50
 
@@ -52,135 +53,85 @@ module PgEventstore
52
53
  def stop_all
53
54
  return self unless @basic_runner.running?
54
55
 
55
- @runners.each(&:stop_async)
56
+ @subscriptions_lifecycle.runners.each(&:stop_async)
56
57
  self
57
58
  end
58
59
 
59
- # Sets the force_lock flash to true. If set - all related Subscriptions will ignore their lock state and will be
60
- # locked by the new SubscriptionsSet.
61
- # @return [void]
62
- def force_lock!
63
- @force_lock = true
64
- end
65
-
66
60
  # Produces a copy of currently running Subscriptions. This is needed, because original Subscriptions objects are
67
61
  # dangerous to use - users may incidentally break their state.
68
62
  # @return [Array<PgEventstore::Subscription>]
69
63
  def read_only_subscriptions
70
- @runners.map(&:subscription).map(&:dup)
64
+ @subscriptions_lifecycle.subscriptions.map(&:dup)
71
65
  end
72
66
 
73
67
  # Produces a copy of current SubscriptionsSet. This is needed, because original SubscriptionsSet object is
74
68
  # dangerous to use - users may incidentally break its state.
75
69
  # @return [PgEventstore::SubscriptionsSet, nil]
76
70
  def read_only_subscriptions_set
77
- @subscriptions_set&.dup
71
+ @subscriptions_set_lifecycle.subscriptions_set&.dup
78
72
  end
79
73
 
80
74
  private
81
75
 
82
- # Locks all Subscriptions behind the current SubscriptionsSet
83
- # @return [void]
84
- def lock_all
85
- @runners.each { |runner| runner.lock!(subscriptions_set.id, force: @force_lock) }
86
- end
87
-
88
- # @return [PgEventstore::SubscriptionsSet]
89
- def subscriptions_set
90
- @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
91
- end
92
-
93
- # @return [PgEventstore::SubscriptionRunnersFeeder]
94
- def feeder
95
- SubscriptionRunnersFeeder.new(@config_name)
96
- end
97
-
98
76
  # @return [void]
99
77
  def attach_runner_callbacks
100
- @basic_runner.define_callback(:change_state, :after, method(:update_subscriptions_set_state))
101
- @basic_runner.define_callback(:before_runner_started, :before, method(:before_runner_started))
102
- @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
103
- @basic_runner.define_callback(:after_runner_died, :after, method(:restart_runner))
104
- @basic_runner.define_callback(:process_async, :before, method(:ping_subscriptions_set))
105
- @basic_runner.define_callback(:process_async, :before, method(:process_async))
106
- @basic_runner.define_callback(:process_async, :after, method(:ping_subscriptions))
107
- @basic_runner.define_callback(:after_runner_stopped, :before, method(:after_runner_stopped))
108
- @basic_runner.define_callback(:before_runner_restored, :after, method(:update_runner_restarts))
109
- end
110
-
111
- # @return [void]
112
- def before_runner_started
113
- lock_all
114
- @runners.each(&:start)
115
- @commands_handler.start
116
- rescue PgEventstore::SubscriptionAlreadyLockedError
117
- @subscriptions_set&.delete
118
- @subscriptions_set = nil
119
- raise
120
- end
121
-
122
- # @param error [StandardError]
123
- # @return [void]
124
- def after_runner_died(error)
125
- subscriptions_set.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
126
- end
127
-
128
- # @param _error [StandardError]
129
- # @return [void]
130
- def restart_runner(_error)
131
- return if subscriptions_set.restart_count >= subscriptions_set.max_restarts_number
132
-
133
- Thread.new do
134
- sleep subscriptions_set.time_between_restarts
135
- restore
136
- end
137
- end
138
-
139
- # @return [void]
140
- def update_runner_restarts
141
- subscriptions_set.update(last_restarted_at: Time.now.utc, restart_count: subscriptions_set.restart_count + 1)
142
- end
143
-
144
- # @return [void]
145
- def process_async
146
- feeder.feed(@runners)
147
- end
148
-
149
- # @return [void]
150
- def ping_subscriptions_set
151
- return if subscriptions_set.updated_at > Time.now.utc - HEARTBEAT_INTERVAL
152
-
153
- subscriptions_set.update(updated_at: Time.now.utc)
154
- end
155
-
156
- # @return [void]
157
- def ping_subscriptions
158
- return if @subscriptions_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
159
-
160
- runners = @runners.select do |runner|
161
- next false unless runner.running?
162
-
163
- runner.subscription.updated_at < Time.now.utc - HEARTBEAT_INTERVAL
164
- end
165
- unless runners.empty?
166
- Subscription.using_connection(@config_name).ping_all(subscriptions_set.id, runners.map(&:subscription))
167
- end
168
-
169
- @subscriptions_pinged_at = Time.now.utc
170
- end
171
-
172
- # @return [void]
173
- def after_runner_stopped
174
- @runners.each(&:stop_async).each(&:wait_for_finish)
175
- @subscriptions_set&.delete
176
- @subscriptions_set = nil
177
- @commands_handler.stop
178
- end
179
-
180
- # @param state [String]
181
- # @return [void]
182
- def update_subscriptions_set_state(state)
183
- subscriptions_set.update(state: state)
78
+ @basic_runner.define_callback(
79
+ :change_state, :after,
80
+ SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_state, @subscriptions_set_lifecycle)
81
+ )
82
+
83
+ @basic_runner.define_callback(
84
+ :before_runner_started, :before,
85
+ SubscriptionFeederHandlers.setup_handler(:lock_subscriptions, @subscriptions_lifecycle)
86
+ )
87
+ @basic_runner.define_callback(
88
+ :before_runner_started, :before,
89
+ SubscriptionFeederHandlers.setup_handler(:start_runners, @subscriptions_lifecycle)
90
+ )
91
+ @basic_runner.define_callback(
92
+ :before_runner_started, :before,
93
+ SubscriptionFeederHandlers.setup_handler(:start_cmds_handler, @commands_handler)
94
+ )
95
+
96
+ @basic_runner.define_callback(
97
+ :after_runner_died, :before,
98
+ SubscriptionFeederHandlers.setup_handler(:persist_error_info, @subscriptions_set_lifecycle)
99
+ )
100
+ @basic_runner.define_callback(
101
+ :after_runner_died, :before,
102
+ SubscriptionFeederHandlers.setup_handler(:restart_runner, @subscriptions_set_lifecycle, @basic_runner)
103
+ )
104
+
105
+ @basic_runner.define_callback(
106
+ :process_async, :before,
107
+ SubscriptionFeederHandlers.setup_handler(:ping_subscriptions_set, @subscriptions_set_lifecycle)
108
+ )
109
+ @basic_runner.define_callback(
110
+ :process_async, :before,
111
+ SubscriptionFeederHandlers.setup_handler(:feed_runners, @subscriptions_lifecycle, @config_name)
112
+ )
113
+ @basic_runner.define_callback(
114
+ :process_async, :after,
115
+ SubscriptionFeederHandlers.setup_handler(:ping_subscriptions, @subscriptions_lifecycle)
116
+ )
117
+
118
+ @basic_runner.define_callback(
119
+ :after_runner_stopped, :before,
120
+ SubscriptionFeederHandlers.setup_handler(:stop_runners, @subscriptions_lifecycle)
121
+ )
122
+ @basic_runner.define_callback(
123
+ :after_runner_stopped, :before,
124
+ SubscriptionFeederHandlers.setup_handler(:reset_subscriptions_set, @subscriptions_set_lifecycle)
125
+ )
126
+ @basic_runner.define_callback(
127
+ :after_runner_stopped, :before,
128
+ SubscriptionFeederHandlers.setup_handler(:stop_commands_handler, @commands_handler)
129
+ )
130
+
131
+ @basic_runner.define_callback(
132
+ :before_runner_restored, :after,
133
+ SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_restarts, @subscriptions_set_lifecycle)
134
+ )
184
135
  end
185
136
 
186
137
  # This method helps to ensure that no Subscription is added after SubscriptionFeeder's runner is working
@@ -188,10 +139,11 @@ module PgEventstore
188
139
  # @raise [RuntimeError]
189
140
  def assert_proper_state!
190
141
  return if @basic_runner.initial? || @basic_runner.stopped?
142
+ subscriptions_set = @subscriptions_set_lifecycle.persisted_subscriptions_set
191
143
 
192
144
  error_message = <<~TEXT
193
- Could not add subscription - #{subscriptions_set.name}##{subscriptions_set.id} must be either in the initial \
194
- or in the stopped state, but it is in the #{@basic_runner.state} state now.
145
+ Could not add subscription - #{subscriptions_set.name}##{subscriptions_set.id} must be \
146
+ either in the initial or in the stopped state, but it is in the #{@basic_runner.state} state now.
195
147
  TEXT
196
148
  raise error_message
197
149
  end
@@ -74,66 +74,41 @@ module PgEventstore
74
74
 
75
75
  # @return [void]
76
76
  def attach_callbacks
77
- @events_processor.define_callback(:process, :around, method(:track_exec_time))
78
- @events_processor.define_callback(:process, :after, method(:update_subscription_stats))
79
- @events_processor.define_callback(:error, :after, method(:update_subscription_error))
80
- @events_processor.define_callback(:error, :after, method(:restart_subscription))
81
- @events_processor.define_callback(:feed, :after, method(:update_subscription_chunk_stats))
82
- @events_processor.define_callback(:restart, :after, method(:update_subscription_restarts))
83
- @events_processor.define_callback(:change_state, :after, method(:update_subscription_state))
84
- end
85
-
86
- # @param action [Proc]
87
- # @return [void]
88
- def track_exec_time(action, *)
89
- @stats.track_exec_time { action.call }
90
- end
91
-
92
- # @param current_position [Integer]
93
- # @return [void]
94
- def update_subscription_stats(current_position)
95
- @subscription.update(
96
- average_event_processing_time: @stats.average_event_processing_time,
97
- current_position: current_position,
98
- total_processed_events: @subscription.total_processed_events + 1
77
+ @events_processor.define_callback(
78
+ :process, :around,
79
+ SubscriptionRunnerHandlers.setup_handler(:track_exec_time, @stats)
80
+ )
81
+ @events_processor.define_callback(
82
+ :process, :after,
83
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_stats, @subscription, @stats)
99
84
  )
100
- end
101
-
102
- # @param state [String]
103
- # @return [void]
104
- def update_subscription_state(state)
105
- @subscription.update(state: state)
106
- end
107
85
 
108
- # @return [void]
109
- def update_subscription_restarts
110
- @subscription.update(last_restarted_at: Time.now.utc, restart_count: @subscription.restart_count + 1)
111
- end
86
+ @events_processor.define_callback(
87
+ :error, :after,
88
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_error, @subscription)
89
+ )
90
+ @events_processor.define_callback(
91
+ :error, :after,
92
+ SubscriptionRunnerHandlers.setup_handler(
93
+ :restart_events_processor,
94
+ @subscription, @restart_terminator, @failed_subscription_notifier, @events_processor
95
+ )
96
+ )
112
97
 
113
- # @param error [StandardError]
114
- # @return [void]
115
- def update_subscription_error(error)
116
- @subscription.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
117
- end
98
+ @events_processor.define_callback(
99
+ :feed, :after,
100
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_chunk_stats, @subscription)
101
+ )
118
102
 
119
- # @param global_position [Integer]
120
- # @return [void]
121
- def update_subscription_chunk_stats(global_position)
122
- @subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
123
- end
103
+ @events_processor.define_callback(
104
+ :restart, :after,
105
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_restarts, @subscription)
106
+ )
124
107
 
125
- # @param error [StandardError]
126
- # @return [void]
127
- def restart_subscription(error)
128
- return if @restart_terminator&.call(@subscription.dup)
129
- if @subscription.restart_count >= @subscription.max_restarts_number
130
- return @failed_subscription_notifier&.call(@subscription.dup, error)
131
- end
132
-
133
- Thread.new do
134
- sleep @subscription.time_between_restarts
135
- restore
136
- end
108
+ @events_processor.define_callback(
109
+ :change_state, :after,
110
+ SubscriptionRunnerHandlers.setup_handler(:update_subscription_state, @subscription)
111
+ )
137
112
  end
138
113
  end
139
114
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class SubscriptionsLifecycle
5
+ extend Forwardable
6
+
7
+ # @return [Integer] number of seconds between heartbeat updates
8
+ HEARTBEAT_INTERVAL = 10 # seconds
9
+
10
+ # @!attribute runners
11
+ # @return [Array<PgEventstore::SubscriptionRunner>]
12
+ attr_reader :runners
13
+
14
+ # @param config_name [Symbol]
15
+ # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
16
+ def initialize(config_name, subscriptions_set_lifecycle)
17
+ @config_name = config_name
18
+ @subscriptions_set_lifecycle = subscriptions_set_lifecycle
19
+ @runners = []
20
+ @subscriptions_pinged_at = Time.at(0)
21
+ @force_lock = false
22
+ end
23
+
24
+ # Locks all Subscriptions behind the current SubscriptionsSet
25
+ # @return [void]
26
+ def lock_all
27
+ @runners.each do |runner|
28
+ runner.lock!(@subscriptions_set_lifecycle.persisted_subscriptions_set.id, force: force_locked?)
29
+ end
30
+ rescue PgEventstore::SubscriptionAlreadyLockedError
31
+ @subscriptions_set_lifecycle.reset_subscriptions_set
32
+ raise
33
+ end
34
+
35
+ # @return [void]
36
+ def ping_subscriptions
37
+ return if @subscriptions_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
38
+
39
+ runners = @runners.select do |runner|
40
+ next false unless runner.running?
41
+
42
+ runner.subscription.updated_at < Time.now.utc - HEARTBEAT_INTERVAL
43
+ end
44
+ unless runners.empty?
45
+ Subscription.using_connection(@config_name).ping_all(
46
+ @subscriptions_set_lifecycle.persisted_subscriptions_set.id, runners.map(&:subscription)
47
+ )
48
+ end
49
+
50
+ @subscriptions_pinged_at = Time.now.utc
51
+ end
52
+
53
+ # @return [Array<PgEventstore::Subscription>]
54
+ def subscriptions
55
+ @runners.map(&:subscription)
56
+ end
57
+
58
+ # Sets the force_lock flag to true. If set - all related Subscriptions will ignore their lock state and will be
59
+ # locked by the new SubscriptionsSet.
60
+ # @return [void]
61
+ def force_lock!
62
+ @force_lock = true
63
+ end
64
+
65
+ # @return [Boolean]
66
+ def force_locked?
67
+ @force_lock
68
+ end
69
+ end
70
+ end
@@ -9,6 +9,12 @@ require_relative 'subscription_handler_performance'
9
9
  require_relative 'subscription_runner'
10
10
  require_relative 'subscriptions_set'
11
11
  require_relative 'subscription_runners_feeder'
12
+ require_relative 'subscriptions_set_lifecycle'
13
+ require_relative 'subscriptions_lifecycle'
14
+ require_relative 'callback_handlers/subscription_feeder_handlers'
15
+ require_relative 'callback_handlers/subscription_runner_handlers'
16
+ require_relative 'callback_handlers/events_processor_handlers'
17
+ require_relative 'callback_handlers/commands_handler_handlers'
12
18
  require_relative 'subscription_feeder'
13
19
  require_relative 'extensions/command_class_lookup_extension'
14
20
  require_relative 'extensions/base_command_extension'
@@ -84,7 +90,7 @@ module PgEventstore
84
90
  runner = SubscriptionRunner.new(
85
91
  stats: SubscriptionHandlerPerformance.new,
86
92
  events_processor: EventsProcessor.new(
87
- create_event_handler(middlewares, handler), graceful_shutdown_timeout: graceful_shutdown_timeout
93
+ create_raw_event_handler(middlewares, handler), graceful_shutdown_timeout: graceful_shutdown_timeout
88
94
  ),
89
95
  subscription: subscription,
90
96
  restart_terminator: restart_terminator,
@@ -118,7 +124,7 @@ module PgEventstore
118
124
  # @param middlewares [Array<Symbol>, nil]
119
125
  # @param handler [#call]
120
126
  # @return [Proc]
121
- def create_event_handler(middlewares, handler)
127
+ def create_raw_event_handler(middlewares, handler)
122
128
  deserializer = EventDeserializer.new(select_middlewares(middlewares), config.event_class_resolver)
123
129
  ->(raw_event) { handler.call(deserializer.deserialize(raw_event)) }
124
130
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class SubscriptionsSetLifecycle
5
+ # @return [Integer] number of seconds between heartbeat updates
6
+ HEARTBEAT_INTERVAL = 10 # seconds
7
+
8
+ # @!attribute subscriptions_set
9
+ # @return [PgEventstore::SubscriptionsSet, nil]
10
+ attr_reader :subscriptions_set
11
+
12
+ # @param config_name [Symbol]
13
+ # @param subscriptions_set_attrs [Hash]
14
+ def initialize(config_name, subscriptions_set_attrs)
15
+ @config_name = config_name
16
+ @subscriptions_set_attrs = subscriptions_set_attrs
17
+ @subscriptions_set_pinged_at = Time.at(0)
18
+ end
19
+
20
+ # @return [void]
21
+ def ping_subscriptions_set
22
+ return if @subscriptions_set_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
23
+
24
+ persisted_subscriptions_set.update(updated_at: Time.now.utc)
25
+ @subscriptions_set_pinged_at = Time.now.utc
26
+ end
27
+
28
+ # @return [PgEventstore::SubscriptionsSet]
29
+ def persisted_subscriptions_set
30
+ @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
31
+ end
32
+
33
+ # @return [void]
34
+ def reset_subscriptions_set
35
+ @subscriptions_set&.delete
36
+ @subscriptions_set = nil
37
+ end
38
+ end
39
+ end
@@ -61,6 +61,14 @@ module PgEventstore
61
61
  str.gsub!(/[A-Z]/) { |letter| '_' + letter.downcase }
62
62
  str
63
63
  end
64
+
65
+ # Detect the global position of the event record in the database. If it is a link event - we pick a
66
+ # global_position of the link instead of picking a global_position of an event this link points to.
67
+ # @param raw_event [Hash]
68
+ # @return [Integer]
69
+ def original_global_position(raw_event)
70
+ raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
71
+ end
64
72
  end
65
73
  end
66
74
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = "1.4.0"
5
+ VERSION = "1.5.0"
6
6
  end
@@ -69,6 +69,11 @@ module PgEventstore
69
69
  def asset_url(path)
70
70
  url("#{path}?v=#{PgEventstore::VERSION}")
71
71
  end
72
+
73
+ # @return [Boolean]
74
+ def resolve_link_tos?
75
+ params.key?(:resolve_link_tos) ? params[:resolve_link_tos] == 'true' : true
76
+ end
72
77
  end
73
78
 
74
79
  get '/' do
@@ -77,7 +82,10 @@ module PgEventstore
77
82
  starting_id: params[:starting_id]&.to_i,
78
83
  per_page: Paginator::EventsCollection::PER_PAGE[params[:per_page]],
79
84
  order: Paginator::EventsCollection::SQL_DIRECTIONS[params[:order]],
80
- options: { filter: { event_types: events_filter, streams: streams_filter } }
85
+ options: {
86
+ filter: { event_types: events_filter, streams: streams_filter },
87
+ resolve_link_tos: resolve_link_tos?
88
+ }
81
89
  )
82
90
 
83
91
  if request.xhr?
@@ -23,9 +23,7 @@ module PgEventstore
23
23
  def collection
24
24
  @_collection ||= PgEventstore.client(config_name).read(
25
25
  PgEventstore::Stream.all_stream,
26
- options: options.merge(
27
- from_position: starting_id, max_count: per_page, direction: order, resolve_link_tos: true
28
- ),
26
+ options: options.merge(from_position: starting_id, max_count: per_page, direction: order),
29
27
  middlewares: []
30
28
  )
31
29
  end
@@ -50,6 +50,10 @@ module PgEventstore
50
50
  build_path(params.merge(order: order))
51
51
  end
52
52
 
53
+ def resolve_link_tos_url(should_resolve)
54
+ build_path(params.merge(resolve_link_tos: should_resolve))
55
+ end
56
+
53
57
  # @param number [Integer] total number of events by the current filter
54
58
  # @return [String]
55
59
  def total_count(number)
@@ -92,7 +96,6 @@ module PgEventstore
92
96
  }
93
97
  }
94
98
  )
95
-
96
99
  end
97
100
 
98
101
  private
@@ -113,12 +113,12 @@ $(function(){
113
113
  initEventTypeFilterAutocomplete($(this));
114
114
  });
115
115
 
116
+ // Automatically refresh events list every two seconds
116
117
  let autoRefreshInterval;
117
118
  $('#auto-refresh').change(function(){
118
119
  if($(this).is(':checked')) {
119
120
  autoRefreshInterval = setInterval(function(){
120
121
  $.getJSON(window.location.href).success(function(response, textStatus, xhr){
121
- console.log(textStatus);
122
122
  if(textStatus === 'success') {
123
123
  $('#events-table').find('tbody').html(response.events);
124
124
  $('#total-count').html(response.total_count);
@@ -131,6 +131,15 @@ $(function(){
131
131
  }
132
132
  });
133
133
 
134
+ // Resolve links checkbox
135
+ $('#resolve-link-tos').change(function(){
136
+ if($(this).is(':checked')) {
137
+ window.location.href = $(this).data('url-checked');
138
+ } else {
139
+ window.location.href = $(this).data('url-unchecked');
140
+ }
141
+ });
142
+
134
143
  // Toggle event's JSON data/metadata details
135
144
  $('#events-table').on('click', '.toggle-event-data', function(){
136
145
  $(this).parents('tr').next().toggleClass('d-none');
@@ -78,13 +78,12 @@ module PgEventstore
78
78
  # @param state [String]
79
79
  # @param updated_at [Time]
80
80
  # @return [String] html status
81
- def colored_state(state, updated_at)
81
+ def colored_state(state, interval, updated_at)
82
82
  if state == RunnerState::STATES[:running]
83
83
  # -1 is added as a margin to prevent false-positive result
84
- if updated_at < Time.now.utc - SubscriptionFeeder::HEARTBEAT_INTERVAL - 1
84
+ if updated_at < Time.now.utc - interval - 1
85
85
  title = <<~TEXT
86
- Something is wrong. Last update was more than #{SubscriptionFeeder::HEARTBEAT_INTERVAL} seconds \
87
- ago(#{updated_at}).
86
+ Something is wrong. Last update was more than #{interval} seconds ago(#{updated_at}).
88
87
  TEXT
89
88
  <<~HTML
90
89
  <span class="text-warning text-nowrap">
@@ -26,6 +26,7 @@
26
26
  <br/>
27
27
  <form id="filters-form" action="<%= url('/') %>" method="GET" data-parsley-validate class="form-horizontal form-label-left">
28
28
  <input type="hidden" name="per_page" value="<%= params[:per_page].to_i %>">
29
+ <input type="hidden" name="resolve_link_tos" value="<%= resolve_link_tos? %>">
29
30
  <div class="stream-filters">
30
31
  <% streams_filter&.each do |attrs| %>
31
32
  <%= erb :'home/partials/stream_filter', { layout: false }, { stream: attrs } %>
@@ -89,6 +90,16 @@
89
90
  <button type="submit" class="d-none"></button>
90
91
  </div>
91
92
  </form>
93
+ <div class="float-md-right mt-2 mr-2">
94
+ <div class="form-group">
95
+ <div class="checkbox">
96
+ <label for="resolve-link-tos" class="control-label">
97
+ Resolve links
98
+ <input type="checkbox" id="resolve-link-tos" value="true" autocomplete="off" <% if resolve_link_tos? %> checked <% end %> data-url-checked="<%= resolve_link_tos_url(true) %>" data-url-unchecked="<%= resolve_link_tos_url(false) %>">
99
+ </label>
100
+ </div>
101
+ </div>
102
+ </div>
92
103
  <div class="float-md-right mt-2 mr-2">
93
104
  <div class="form-group">
94
105
  <div class="checkbox">