pg_eventstore 1.1.5 → 1.3.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/docs/configuration.md +1 -0
  4. data/docs/subscriptions.md +4 -2
  5. data/lib/pg_eventstore/config.rb +4 -0
  6. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +5 -2
  7. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +15 -0
  8. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +9 -4
  9. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +3 -1
  10. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/restore.rb +8 -0
  11. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +10 -4
  12. data/lib/pg_eventstore/version.rb +1 -1
  13. data/lib/pg_eventstore/web/application.rb +16 -0
  14. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +2 -2
  15. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +5 -4
  16. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +2 -2
  17. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +11 -0
  18. data/lib/pg_eventstore/web/subscriptions/helpers.rb +15 -0
  19. data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +35 -0
  20. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +39 -0
  21. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions_set.rb +40 -0
  22. data/lib/pg_eventstore/web/views/subscriptions/index.erb +13 -1
  23. data/lib/pg_eventstore/web.rb +3 -0
  24. data/pg_eventstore.gemspec +1 -1
  25. data/sig/interfaces/failed_subscription_notifier.rbs +3 -0
  26. data/sig/pg_eventstore/config.rbs +2 -0
  27. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +1 -1
  28. data/sig/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rbs +2 -0
  29. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +5 -2
  30. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +5 -2
  31. data/sig/pg_eventstore/web/subscriptions/helpers.rbs +4 -0
  32. data/sig/pg_eventstore/web/subscriptions/with_state/set_collection.rbs +21 -0
  33. data/sig/pg_eventstore/web/subscriptions/with_state/subscriptions.rbs +23 -0
  34. data/sig/pg_eventstore/web/subscriptions/with_state/subscriptions_set.rbs +23 -0
  35. metadata +19 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fce9f1f61310bfc9c12afdb89b0a6589e9116133255eeb62ac406b24e351b72
4
- data.tar.gz: 0a03a441f9e26a040f026fc7fac5d034c81b91d6f0e0bee98646b39a2f809147
3
+ metadata.gz: 8691b2cf8e831e31da17b71fc06078a25d73ae1e7e1a0b9da629a481a4947842
4
+ data.tar.gz: 9022a6bdac6ef9aadc7338b54fe5a9fea76cdcb5ea7f2a7dc78fdcd98b2f469b
5
5
  SHA512:
6
- metadata.gz: 26a0a9b9cf70e32fd73d5f729d5d9654ec23cd90aa57a3bfdf4cae5603f58d56d62a1406df477200390497141e5c4bb89f157c749bd1df680c1b404898428094
7
- data.tar.gz: 85aeeffcfc9ddfba2733e7362958a9d1528eb043ae5025c2d8bd055aa708e5c20eb2fef555be711174f3c058235c0f5b97c780b9f68b3a52e92592fe58d8f8c3
6
+ metadata.gz: 0efd8f80acbee784665bfc455239ea02b336335e2d6f36341ea65d86f973b126794f306373b66b6383cd74ba12b8566b2544bda9ebf5efc9d7d1d3611c6276e3
7
+ data.tar.gz: 31c54e37a0fae15add5af3c03ff8c4a6db41812feb8012bca2f242d245da863c4c99b78465eb39684bdf4259d8eff675493cc690c54a31e49fc15b06e941c42c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.3.0]
4
+ - Add ability to filter subscriptions by state in admin UI
5
+ - Reset error-related subscription's attributes on subscription restore
6
+ - Reset total processed events number when user changes subscription's position
7
+ - Allow to search event type, stream context and stream name by substring in web UI
8
+ - Relax sinatra version requirement to v3+
9
+
10
+ ## [1.2.0]
11
+ - Implement `failed_subscription_notifier` subscription hook.
12
+
13
+ Now you are able to define a function that is called when subscription fails and no longer can be automatically restarted because it hit max number of retries. You can define the hook globally in the config and per subscription. Examples:
14
+
15
+ ```ruby
16
+ PgEventstore.configure do |config|
17
+ config.failed_subscription_notifier = proc { |sub, error| puts "Subscription: #{sub.inspect}, error: #{error.inspect}" }
18
+ end
19
+
20
+ subscriptions_manager = PgEventstore.subscriptions_manager(subscription_set: 'MyApp')
21
+ # Overrides config.failed_subscription_notifier function
22
+ subscriptions_manager.subscribe(
23
+ 'My Subscription 1',
24
+ handler: Handler.new('My Subscription 1'),
25
+ options: { filter: { event_types: ['Foo'] } },
26
+ failed_subscription_notifier: proc { |_subscription, err| p err }
27
+ )
28
+ # Uses config.failed_subscription_notifier function
29
+ subscriptions_manager.subscribe(
30
+ 'My Subscription 2',
31
+ handler: Handler.new('My Subscription 2'),
32
+ options: { filter: { event_types: ['Bar'] } }
33
+ )
34
+ ```
35
+
3
36
  ## [1.1.5]
4
37
  - Review the way to handle SubscriptionAlreadyLockedError error. This removes noise when attempting to lock an already locked subscription.
5
38
 
@@ -16,6 +16,7 @@ Configuration options:
16
16
  | subscriptions_set_max_retries | Integer | `10` | Max number of retries for failed subscription sets. |
17
17
  | subscriptions_set_retries_interval | Integer | `1` | Interval in seconds between retries of failed subscription sets. |
18
18
  | subscription_restart_terminator | `#call` | `nil` | A callable object that accepts `PgEventstore::Subscription` object to determine whether restarts should be stopped(true - stops restarts, false - continues restarts). |
19
+ | failed_subscription_notifier | `#call` | `nil` | A callable object which is invoked with `PgEventstore::Subscription` instance and error instance after the related subscription died due to error and no longer can be automatically restarted due to max retries number reached. You can use this hook to send a notification about failed subscription. |
19
20
 
20
21
  ## Multiple configurations
21
22
 
@@ -151,7 +151,7 @@ You will then see the output of your subscription handlers. To gracefully stop t
151
151
 
152
152
  ## Overriding Subscription config values
153
153
 
154
- You can override `subscription_pull_interval`, `subscription_max_retries`, `subscription_retries_interval` and `subscription_restart_terminator` config values (see [**Configuration**](configuration.md) chapter for details) for the specific subscription by providing the corresponding arguments. Example:
154
+ You can override `subscription_pull_interval`, `subscription_max_retries`, `subscription_retries_interval`, `subscription_restart_terminator` and `failed_subscription_notifier` config values (see [**Configuration**](configuration.md) chapter for details) for the specific subscription by providing the corresponding arguments. Example:
155
155
 
156
156
  ```ruby
157
157
  subscriptions_manager.subscribe(
@@ -164,7 +164,9 @@ subscriptions_manager.subscribe(
164
164
  # overrides config.subscription_retries_interval
165
165
  retries_interval: 2,
166
166
  # overrides config.subscription_restart_terminator
167
- restart_terminator: proc { |subscription| subscription.last_error['class'] == 'NoMethodError' },
167
+ restart_terminator: proc { |subscription| subscription.last_error['class'] == 'NoMethodError' },
168
+ # overrides config.failed_subscription_notifier
169
+ failed_subscription_notifier: proc { |_subscription, err| p err }
168
170
  )
169
171
  ```
170
172
 
@@ -48,6 +48,10 @@ module PgEventstore
48
48
  # @!attribute subscriptions_set_retries_interval
49
49
  # @return [Integer] interval in seconds between retries of failed SubscriptionsSet
50
50
  option(:subscriptions_set_retries_interval) { 1 }
51
+ # @!attribute failed_subscription_notifier
52
+ # @return [#call, nil] provide callable object that accepts Subscription instance and error. It is useful when you
53
+ # want to be get notified when your Subscription fails and no longer can be restarted
54
+ option(:failed_subscription_notifier)
51
55
 
52
56
  # @param name [Symbol] config's name. Its value matches the appropriate key in PgEventstore.config hash
53
57
  def initialize(name:, **options)
@@ -45,10 +45,13 @@ module PgEventstore
45
45
  pg_result.map(&method(:deserialize))
46
46
  end
47
47
 
48
+ # @param state [String, nil]
48
49
  # @return [Array<String>]
49
- def set_collection
50
+ def set_collection(state = nil)
51
+ builder = SQLBuilder.new.from('subscriptions').select('set').group('set').order('set ASC')
52
+ builder.where('state = ?', state) if state
50
53
  connection.with do |conn|
51
- conn.exec('SELECT set FROM subscriptions GROUP BY set ORDER BY set ASC')
54
+ conn.exec_params(*builder.to_exec_params)
52
55
  end.map { |attrs| attrs['set'] }
53
56
  end
54
57
 
@@ -27,6 +27,21 @@ module PgEventstore
27
27
  pg_result.to_a.map(&method(:deserialize))
28
28
  end
29
29
 
30
+ # @param name [String, nil]
31
+ # @param state [String]
32
+ # @return [Array<Hash>]
33
+ def find_all_by_subscription_state(name:, state:)
34
+ builder = SQLBuilder.new.select('subscriptions_set.*').from('subscriptions_set')
35
+ builder.join('JOIN subscriptions ON subscriptions.locked_by = subscriptions_set.id')
36
+ builder.order('subscriptions_set.name ASC, subscriptions_set.id ASC')
37
+ builder.where('subscriptions_set.name = ? and subscriptions.state = ?', name, state)
38
+ builder.group('subscriptions_set.id')
39
+ pg_result = connection.with do |conn|
40
+ conn.exec_params(*builder.to_exec_params)
41
+ end
42
+ pg_result.to_a.map(&method(:deserialize))
43
+ end
44
+
30
45
  # @return [Array<String>]
31
46
  def set_names
32
47
  builder = SQLBuilder.new.select('name').from('subscriptions_set').group('name').order('name ASC')
@@ -29,11 +29,14 @@ module PgEventstore
29
29
  # @param events_processor [PgEventstore::EventsProcessor]
30
30
  # @param subscription [PgEventstore::Subscription]
31
31
  # @param restart_terminator [#call, nil]
32
- def initialize(stats:, events_processor:, subscription:, restart_terminator: nil)
32
+ # @param failed_subscription_notifier [#call, nil]
33
+ def initialize(stats:, events_processor:, subscription:, restart_terminator: nil,
34
+ failed_subscription_notifier: nil)
33
35
  @stats = stats
34
36
  @events_processor = events_processor
35
37
  @subscription = subscription
36
38
  @restart_terminator = restart_terminator
39
+ @failed_subscription_notifier = failed_subscription_notifier
37
40
 
38
41
  attach_callbacks
39
42
  end
@@ -119,11 +122,13 @@ module PgEventstore
119
122
  @subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
120
123
  end
121
124
 
122
- # @param _error [StandardError]
125
+ # @param error [StandardError]
123
126
  # @return [void]
124
- def restart_subscription(_error)
127
+ def restart_subscription(error)
125
128
  return if @restart_terminator&.call(@subscription.dup)
126
- return if @subscription.restart_count >= @subscription.max_restarts_number
129
+ if @subscription.restart_count >= @subscription.max_restarts_number
130
+ return @failed_subscription_notifier&.call(@subscription.dup, error)
131
+ end
127
132
 
128
133
  Thread.new do
129
134
  sleep @subscription.time_between_restarts
@@ -17,7 +17,9 @@ module PgEventstore
17
17
  def exec_cmd(subscription_runner)
18
18
  subscription_runner.within_state(:stopped) do
19
19
  subscription_runner.clear_chunk
20
- subscription_runner.subscription.update(last_chunk_greatest_position: nil, current_position: data['position'])
20
+ subscription_runner.subscription.update(
21
+ last_chunk_greatest_position: nil, current_position: data['position'], total_processed_events: 0
22
+ )
21
23
  end
22
24
  end
23
25
  end
@@ -7,6 +7,14 @@ module PgEventstore
7
7
  # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
8
  # @return [void]
9
9
  def exec_cmd(subscription_runner)
10
+ subscription_runner.within_state(:dead) do
11
+ subscription_runner.subscription.update(
12
+ restart_count: 0,
13
+ last_restarted_at: nil,
14
+ last_error: nil,
15
+ last_error_occurred_at: nil
16
+ )
17
+ end
10
18
  subscription_runner.restore
11
19
  end
12
20
  end
@@ -61,14 +61,19 @@ module PgEventstore
61
61
  # given subscription.
62
62
  # @param max_retries [Integer] max number of retries of failed Subscription
63
63
  # @param retries_interval [Integer, Float] a delay between retries of failed Subscription
64
- # @param restart_terminator [#call, nil] a callable object which, when called - accepts PgEventstore::Subscription
65
- # object to determine whether restarts should be stopped(true - stops restarts, false - continues restarts)
64
+ # @param restart_terminator [#call, nil] a callable object which is invoked with PgEventstore::Subscription instance
65
+ # to determine whether restarts should be stopped(true - stops restarts, false - continues restarts)
66
+ # @param failed_subscription_notifier [#call, nil] a callable object which is invoked with
67
+ # PgEventstore::Subscription instance and error instance after the related subscription died due to error and no
68
+ # longer can be automatically restarted due to max retries number reached. You can use this hook to send a
69
+ # notification about failed subscription.
66
70
  # @return [void]
67
71
  def subscribe(subscription_name, handler:, options: {}, middlewares: nil,
68
72
  pull_interval: config.subscription_pull_interval,
69
73
  max_retries: config.subscription_max_retries,
70
74
  retries_interval: config.subscription_retries_interval,
71
- restart_terminator: config.subscription_restart_terminator)
75
+ restart_terminator: config.subscription_restart_terminator,
76
+ failed_subscription_notifier: config.failed_subscription_notifier)
72
77
  subscription = Subscription.using_connection(config.name).new(
73
78
  set: @set_name, name: subscription_name, options: options, chunk_query_interval: pull_interval,
74
79
  max_restarts_number: max_retries, time_between_restarts: retries_interval
@@ -77,7 +82,8 @@ module PgEventstore
77
82
  stats: SubscriptionHandlerPerformance.new,
78
83
  events_processor: EventsProcessor.new(create_event_handler(middlewares, handler)),
79
84
  subscription: subscription,
80
- restart_terminator: restart_terminator
85
+ restart_terminator: restart_terminator,
86
+ failed_subscription_notifier: failed_subscription_notifier
81
87
  )
82
88
 
83
89
  @subscription_feeder.add(runner)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = "1.1.5"
5
+ VERSION = "1.3.0"
6
6
  end
@@ -102,6 +102,22 @@ module PgEventstore
102
102
  erb :'subscriptions/index'
103
103
  end
104
104
 
105
+ get '/subscriptions/:state' do
106
+ @set_collection = Subscriptions::WithState::SetCollection.new(connection, state: params[:state])
107
+ @current_set = params[:set_name] || @set_collection.names.first
108
+ subscriptions_set = Subscriptions::WithState::SubscriptionsSet.new(
109
+ connection, @current_set, state: params[:state]
110
+ ).subscriptions_set
111
+ subscriptions = Subscriptions::WithState::Subscriptions.new(
112
+ connection, @current_set, state: params[:state]
113
+ ).subscriptions
114
+ @association = Subscriptions::SubscriptionsToSetAssociation.new(
115
+ subscriptions_set: subscriptions_set,
116
+ subscriptions: subscriptions
117
+ )
118
+ erb :'subscriptions/index'
119
+ end
120
+
105
121
  post '/change_config' do
106
122
  config = params[:config]&.to_sym
107
123
  config = :default unless PgEventstore.available_configs.include?(config)
@@ -16,7 +16,7 @@ module PgEventstore
16
16
  where('context is not null and stream_name is not null').
17
17
  group('event_type').order("event_type #{order}").limit(per_page)
18
18
  sql_builder.where("event_type #{direction_operator} ?", starting_id) if starting_id
19
- sql_builder.where('event_type ilike ?', "#{options[:query]}%")
19
+ sql_builder.where('event_type ilike ?', "%#{options[:query]}%")
20
20
  connection.with do |conn|
21
21
  conn.exec_params(*sql_builder.to_exec_params)
22
22
  end.to_a
@@ -32,7 +32,7 @@ module PgEventstore
32
32
  SQLBuilder.new.select('event_type').from('partitions').
33
33
  where('context is not null and stream_name is not null').
34
34
  where("event_type #{direction_operator} ?", starting_id).
35
- where('event_type ilike ?', "#{options[:query]}%").
35
+ where('event_type ilike ?', "%#{options[:query]}%").
36
36
  group('event_type').order("event_type #{order}").limit(1).offset(per_page)
37
37
 
38
38
  connection.with do |conn|
@@ -12,10 +12,11 @@ module PgEventstore
12
12
  @_collection ||=
13
13
  begin
14
14
  sql_builder =
15
- SQLBuilder.new.select('context').from('partitions').where('stream_name is null and event_type is null')
16
- .limit(per_page).order("context #{order}")
15
+ SQLBuilder.new.select('context').from('partitions').
16
+ where('stream_name is null and event_type is null').
17
+ limit(per_page).order("context #{order}")
17
18
  sql_builder.where("context #{direction_operator} ?", starting_id) if starting_id
18
- sql_builder.where('context ilike ?', "#{options[:query]}%")
19
+ sql_builder.where('context ilike ?', "%#{options[:query]}%")
19
20
  connection.with do |conn|
20
21
  conn.exec_params(*sql_builder.to_exec_params)
21
22
  end.to_a
@@ -29,7 +30,7 @@ module PgEventstore
29
30
  starting_id = collection.first['context']
30
31
  sql_builder =
31
32
  SQLBuilder.new.select('context').from('partitions').where('stream_name is null and event_type is null').
32
- where("context #{direction_operator} ?", starting_id).where('context ilike ?', "#{options[:query]}%").
33
+ where("context #{direction_operator} ?", starting_id).where('context ilike ?', "%#{options[:query]}%").
33
34
  limit(1).offset(per_page).order("context #{order}")
34
35
 
35
36
  connection.with do |conn|
@@ -14,7 +14,7 @@ module PgEventstore
14
14
  sql_builder =
15
15
  SQLBuilder.new.select('stream_name').from('partitions').
16
16
  where('event_type is null and context = ?', options[:context]).
17
- where('stream_name ilike ?', "#{options[:query]}%")
17
+ where('stream_name ilike ?', "%#{options[:query]}%")
18
18
  sql_builder.where("stream_name #{direction_operator} ?", starting_id) if starting_id
19
19
  sql_builder.limit(per_page).order("stream_name #{order}")
20
20
  connection.with do |conn|
@@ -31,7 +31,7 @@ module PgEventstore
31
31
  sql_builder =
32
32
  SQLBuilder.new.select('stream_name').from('partitions').
33
33
  where("stream_name #{direction_operator} ?", starting_id).
34
- where('stream_name ilike ?', "#{options[:query]}%").
34
+ where('stream_name ilike ?', "%#{options[:query]}%").
35
35
  where('event_type is null and context = ?', options[:context]).
36
36
  limit(1).offset(per_page).order("stream_name #{order}")
37
37
 
@@ -215,3 +215,14 @@ $(function(){
215
215
  $(this).find('form').attr('action', $clickedLink.data('url'));
216
216
  });
217
217
  });
218
+
219
+ // Display subscriptions of the selected state
220
+ $(function(){
221
+ "use strict";
222
+
223
+ let $subscriptionsState = $('#subscriptions-state');
224
+ $subscriptionsState.change(function(){
225
+ let $selected = $(this).find('option:selected');
226
+ window.location.href = $selected.data('url');
227
+ });
228
+ });
@@ -13,6 +13,21 @@ module PgEventstore
13
13
  url("/subscriptions?#{encoded_params}")
14
14
  end
15
15
 
16
+ # @param state [String]
17
+ # @return [String]
18
+ def subscriptions_state_url(state:, **params)
19
+ params = params.compact
20
+ return url("/subscriptions/#{state}") if params.empty?
21
+
22
+ encoded_params = Rack::Utils.build_nested_query(params)
23
+ url("/subscriptions/#{state}?#{encoded_params}")
24
+ end
25
+
26
+ # @return [String, nil]
27
+ def subscriptions_state
28
+ params[:state] if PgEventstore::RunnerState::STATES.include?(params[:state])
29
+ end
30
+
16
31
  # @param set_id [Integer]
17
32
  # @param id [Integer]
18
33
  # @param cmd [String]
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Web
5
+ module Subscriptions
6
+ module WithState
7
+ class SetCollection
8
+ # @!attribute connection
9
+ # @return [PgEventstore::Connection]
10
+ attr_reader :connection
11
+ private :connection
12
+
13
+ # @param connection [PgEventstore::Connection]
14
+ # @param state [String]
15
+ def initialize(connection, state:)
16
+ @connection = connection
17
+ @state = state
18
+ end
19
+
20
+ # @return [Array<String>]
21
+ def names
22
+ @set_collection ||= subscription_queries.set_collection(@state)
23
+ end
24
+
25
+ private
26
+
27
+ # @return [PgEventstore::SubscriptionQueries]
28
+ def subscription_queries
29
+ SubscriptionQueries.new(connection)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Web
5
+ module Subscriptions
6
+ module WithState
7
+ class Subscriptions
8
+ # @!attribute connection
9
+ # @return [PgEventstore::Connection]
10
+ attr_reader :connection
11
+ private :connection
12
+
13
+ # @param connection [PgEventstore::Connection]
14
+ # @param current_set [String, nil]
15
+ # @param state [String]
16
+ def initialize(connection, current_set, state:)
17
+ @connection = connection
18
+ @current_set = current_set
19
+ @state = state
20
+ end
21
+
22
+ # @return [Array<PgEventstore::Subscription>]
23
+ def subscriptions
24
+ @subscriptions ||= subscription_queries.find_all(set: @current_set, state: @state).map do |attrs|
25
+ Subscription.new(**attrs)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # @return [PgEventstore::SubscriptionQueries]
32
+ def subscription_queries
33
+ SubscriptionQueries.new(connection)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Web
5
+ module Subscriptions
6
+ module WithState
7
+ class SubscriptionsSet
8
+ # @!attribute connection
9
+ # @return [PgEventstore::Connection]
10
+ attr_reader :connection
11
+ private :connection
12
+
13
+ # @param connection [PgEventstore::Connection]
14
+ # @param current_set [String, nil]
15
+ # @param state [String]
16
+ def initialize(connection, current_set, state:)
17
+ @connection = connection
18
+ @current_set = current_set
19
+ @state = state
20
+ end
21
+
22
+ # @return [Array<PgEventstore::SubscriptionsSet>]
23
+ def subscriptions_set
24
+ @subscriptions_set ||=
25
+ subscriptions_set_queries.find_all_by_subscription_state(name: @current_set, state: @state).map do |attrs|
26
+ PgEventstore::SubscriptionsSet.new(**attrs)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # @return [PgEventstore::SubscriptionsSetQueries]
33
+ def subscriptions_set_queries
34
+ SubscriptionsSetQueries.new(connection)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -4,6 +4,18 @@
4
4
  <div class="title_left">
5
5
  <h3>Subscriptions</h3>
6
6
  </div>
7
+ <div class="title_right">
8
+ <div class="col-md-5 col-sm-5 form-group pull-right top_search">
9
+ <div class="input-group">
10
+ <select class="form-control" id="subscriptions-state" autocomplete="off">
11
+ <option value="" data-url="<%= subscriptions_url %>" <% unless subscriptions_state %> selected <% end %>>All</option>
12
+ <% PgEventstore::RunnerState::STATES.values.each do |state| %>
13
+ <option value="<%= state %>" <% if state == subscriptions_state %> selected <% end %> data-url="<%= subscriptions_state_url(state: state) %>"><%= state.capitalize %></option>
14
+ <% end %>
15
+ </select>
16
+ </div>
17
+ </div>
18
+ </div>
7
19
  </div>
8
20
 
9
21
  <div class="clearfix"></div>
@@ -11,7 +23,7 @@
11
23
  <ul class="nav nav-tabs bg-white">
12
24
  <% @set_collection.names.each do |set_name| %>
13
25
  <li class="nav-item">
14
- <a class="nav-link <%= "active bg-dark text-white" if @current_set == set_name %>" href="<%= subscriptions_url(set_name: set_name) %>">
26
+ <a class="nav-link <%= "active bg-dark text-white" if @current_set == set_name %>" href="<%= subscriptions_state ? subscriptions_state_url(state: subscriptions_state, set_name: set_name) : subscriptions_url(set_name: set_name) %>">
15
27
  <%= set_name %>
16
28
  </a>
17
29
  </li>
@@ -13,6 +13,9 @@ require_relative 'web/subscriptions/set_collection'
13
13
  require_relative 'web/subscriptions/subscriptions'
14
14
  require_relative 'web/subscriptions/subscriptions_set'
15
15
  require_relative 'web/subscriptions/subscriptions_to_set_association'
16
+ require_relative 'web/subscriptions/with_state/set_collection'
17
+ require_relative 'web/subscriptions/with_state/subscriptions'
18
+ require_relative 'web/subscriptions/with_state/subscriptions_set'
16
19
  require_relative 'web/subscriptions/helpers'
17
20
  require_relative 'web/application'
18
21
 
@@ -37,5 +37,5 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.add_dependency "pg", "~> 1.5"
39
39
  spec.add_dependency "connection_pool", "~> 2.4"
40
- spec.add_dependency "sinatra", "~> 4.0"
40
+ spec.add_dependency "sinatra", ">= 3", '< 5'
41
41
  end
@@ -0,0 +1,3 @@
1
+ interface _FailedSubscriptionNotifier
2
+ def call: (PgEventstore::Subscription subscription, StandardError error) -> void
3
+ end
@@ -44,6 +44,8 @@ module PgEventstore
44
44
 
45
45
  attr_accessor subscription_restart_terminator: _RestartTerminator?
46
46
 
47
+ attr_accessor failed_subscription_notifier: _FailedSubscriptionNotifier?
48
+
47
49
  attr_accessor subscriptions_set_max_retries: Integer
48
50
 
49
51
  attr_accessor subscriptions_set_retries_interval: Integer
@@ -12,7 +12,7 @@ module PgEventstore
12
12
  # _@param_ `attrs`
13
13
  def find_all: (::Hash[untyped, untyped] attrs) -> ::Array[::Hash[untyped, untyped]]
14
14
 
15
- def set_collection: () -> ::Array[String]
15
+ def set_collection: (?String? state) -> ::Array[String]
16
16
 
17
17
  # _@param_ `id`
18
18
  def find!: (Integer id) -> ::Hash[untyped, untyped]
@@ -11,6 +11,8 @@ module PgEventstore
11
11
  # The same as #find_all, but returns first result
12
12
  def find_by: (::Hash[untyped, untyped] attrs) -> ::Hash[untyped, untyped]?
13
13
 
14
+ def find_all_by_subscription_state: (name: String?, state: String) -> ::Array[::Hash[untyped, untyped]]
15
+
14
16
  # _@param_ `id`
15
17
  def find!: (Integer id) -> ::Hash[untyped, untyped]
16
18
 
@@ -12,11 +12,14 @@ module PgEventstore
12
12
  # _@param_ `subscription`
13
13
  #
14
14
  # _@param_ `restart_terminator`
15
+ #
16
+ # _@param_ `failed_subscription_notifier`
15
17
  def initialize: (
16
18
  stats: PgEventstore::SubscriptionHandlerPerformance,
17
19
  events_processor: PgEventstore::EventsProcessor,
18
20
  subscription: PgEventstore::Subscription,
19
- ?restart_terminator: _RestartTerminator?
21
+ ?restart_terminator: _RestartTerminator?,
22
+ ?failed_subscription_notifier: _FailedSubscriptionNotifier?
20
23
  ) -> void
21
24
 
22
25
  def next_chunk_query_opts: () -> ::Hash[untyped, untyped]
@@ -47,7 +50,7 @@ module PgEventstore
47
50
  def update_subscription_chunk_stats: (Integer global_position) -> void
48
51
 
49
52
  # _@param_ `_error`
50
- def restart_subscription: (StandardError _error) -> void
53
+ def restart_subscription: (StandardError error) -> void
51
54
 
52
55
  # Returns the value of attribute subscription.
53
56
  attr_accessor subscription: PgEventstore::Subscription
@@ -30,7 +30,9 @@ module PgEventstore
30
30
  #
31
31
  # _@param_ `retries_interval` — a delay between retries of failed Subscription
32
32
  #
33
- # _@param_ `restart_terminator` — a callable object which, when called - accepts PgEventstore::Subscription object to determine whether restarts should be stopped(true - stops restarts, false - continues restarts)
33
+ # _@param_ `restart_terminator` — a callable object which is invoked with PgEventstore::Subscription instance to determine whether restarts should be stopped(true - stops restarts, false - continues restarts)
34
+ #
35
+ # _@param_ `failed_subscription_notifier` - a callable object which is invoked with PgEventstore::Subscription instance and error instance after the related subscription died due to error and no longer can be automatically restarted due to max retries number reached. You can use this hook to send a notification about failed subscription.
34
36
  def subscribe: (
35
37
  String subscription_name,
36
38
  handler: _SubscriptionHandler,
@@ -39,7 +41,8 @@ module PgEventstore
39
41
  ?pull_interval: Integer | Float,
40
42
  ?max_retries: Integer,
41
43
  ?retries_interval: Integer | Float,
42
- ?restart_terminator: _RestartTerminator?
44
+ ?restart_terminator: _RestartTerminator?,
45
+ ?failed_subscription_notifier: _FailedSubscriptionNotifier?
43
46
  ) -> void
44
47
 
45
48
  def subscriptions: () -> ::Array[PgEventstore::Subscription]
@@ -5,6 +5,10 @@ module PgEventstore
5
5
  # _@param_ `set_name`
6
6
  def subscriptions_url: (?set_name: String?) -> String
7
7
 
8
+ def subscriptions_state_url: (state: String, **untyped params) -> String
9
+
10
+ def subscriptions_state: -> String?
11
+
8
12
  # _@param_ `set_id`
9
13
  #
10
14
  # _@param_ `id`
@@ -0,0 +1,21 @@
1
+ module PgEventstore
2
+ module Web
3
+ module Subscriptions
4
+ module WithState
5
+ class SetCollection
6
+ @state: String
7
+
8
+ attr_reader connection: PgEventstore::Connection
9
+
10
+ def initialize: (PgEventstore::Connection connection, state: String) -> void
11
+
12
+ def names: -> Array[String]
13
+
14
+ private
15
+
16
+ def subscription_queries: -> PgEventstore::SubscriptionQueries
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module PgEventstore
2
+ module Web
3
+ module Subscriptions
4
+ module WithState
5
+ class Subscriptions
6
+ @current_set: String?
7
+
8
+ @state: String
9
+
10
+ attr_reader connection: PgEventstore::Connection
11
+
12
+ def initialize: (PgEventstore::Connection connection, String? current_set, state: String) -> void
13
+
14
+ def subscriptions: -> Array[PgEventstore::Subscription]
15
+
16
+ private
17
+
18
+ def subscription_queries: -> PgEventstore::SubscriptionQueries
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module PgEventstore
2
+ module Web
3
+ module Subscriptions
4
+ module WithState
5
+ class SubscriptionsSet
6
+ @current_set: String?
7
+
8
+ @state: String
9
+
10
+ attr_reader connection: PgEventstore::Connection
11
+
12
+ def initialize: (PgEventstore::Connection connection, String? current_set, state: String) -> untyped
13
+
14
+ def subscriptions_set: -> Array[PgEventstore::SubscriptionsSet]
15
+
16
+ private
17
+
18
+ def subscriptions_set_queries: -> PgEventstore::SubscriptionsSetQueries
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-25 00:00:00.000000000 Z
11
+ date: 2024-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -42,16 +42,22 @@ dependencies:
42
42
  name: sinatra
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ - - "<"
46
49
  - !ruby/object:Gem::Version
47
- version: '4.0'
50
+ version: '5'
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
- - - "~>"
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3'
58
+ - - "<"
53
59
  - !ruby/object:Gem::Version
54
- version: '4.0'
60
+ version: '5'
55
61
  description: EventStore implementation using PostgreSQL
56
62
  email:
57
63
  - ivan.dzyzenko@gmail.com
@@ -182,6 +188,9 @@ files:
182
188
  - lib/pg_eventstore/web/subscriptions/subscriptions.rb
183
189
  - lib/pg_eventstore/web/subscriptions/subscriptions_set.rb
184
190
  - lib/pg_eventstore/web/subscriptions/subscriptions_to_set_association.rb
191
+ - lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb
192
+ - lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb
193
+ - lib/pg_eventstore/web/subscriptions/with_state/subscriptions_set.rb
185
194
  - lib/pg_eventstore/web/views/home/dashboard.erb
186
195
  - lib/pg_eventstore/web/views/home/partials/event_filter.erb
187
196
  - lib/pg_eventstore/web/views/home/partials/events.erb
@@ -195,6 +204,7 @@ files:
195
204
  - sig/interfaces/callback.rbs
196
205
  - sig/interfaces/event_class_resolver.rbs
197
206
  - sig/interfaces/event_modifier.rbs
207
+ - sig/interfaces/failed_subscription_notifier.rbs
198
208
  - sig/interfaces/restart_terminator.rbs
199
209
  - sig/interfaces/subscription_handler.rbs
200
210
  - sig/pg/basic_type_registry.rbs
@@ -277,6 +287,9 @@ files:
277
287
  - sig/pg_eventstore/web/subscriptions/subscriptions.rbs
278
288
  - sig/pg_eventstore/web/subscriptions/subscriptions_set.rbs
279
289
  - sig/pg_eventstore/web/subscriptions/subscriptions_to_set_association.rbs
290
+ - sig/pg_eventstore/web/subscriptions/with_state/set_collection.rbs
291
+ - sig/pg_eventstore/web/subscriptions/with_state/subscriptions.rbs
292
+ - sig/pg_eventstore/web/subscriptions/with_state/subscriptions_set.rbs
280
293
  homepage: https://github.com/yousty/pg_eventstore
281
294
  licenses:
282
295
  - MIT