pg_eventstore 1.5.0 → 1.7.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +49 -0
  4. data/db/migrations/7_support_reading_streams_system_stream.sql +2 -0
  5. data/docs/reading_events.md +16 -1
  6. data/docs/subscriptions.md +14 -56
  7. data/exe/pg-eventstore +16 -0
  8. data/lib/pg_eventstore/callbacks.rb +16 -0
  9. data/lib/pg_eventstore/cli/commands/base_command.rb +45 -0
  10. data/lib/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rb +38 -0
  11. data/lib/pg_eventstore/cli/commands/help_command.rb +22 -0
  12. data/lib/pg_eventstore/cli/commands/start_subscriptions_command.rb +96 -0
  13. data/lib/pg_eventstore/cli/commands/stop_subscriptions_command.rb +22 -0
  14. data/lib/pg_eventstore/cli/commands.rb +6 -0
  15. data/lib/pg_eventstore/cli/exit_codes.rb +12 -0
  16. data/lib/pg_eventstore/cli/parser_options/base_options.rb +46 -0
  17. data/lib/pg_eventstore/cli/parser_options/default_options.rb +10 -0
  18. data/lib/pg_eventstore/cli/parser_options/metadata.rb +34 -0
  19. data/lib/pg_eventstore/cli/parser_options/subscription_options.rb +31 -0
  20. data/lib/pg_eventstore/cli/parser_options.rb +6 -0
  21. data/lib/pg_eventstore/cli/parsers/base_parser.rb +33 -0
  22. data/lib/pg_eventstore/cli/parsers/default_parser.rb +24 -0
  23. data/lib/pg_eventstore/cli/parsers/subscription_parser.rb +24 -0
  24. data/lib/pg_eventstore/cli/parsers.rb +5 -0
  25. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +75 -0
  26. data/lib/pg_eventstore/cli/try_unlock_subscriptions_set.rb +16 -0
  27. data/lib/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rb +67 -0
  28. data/lib/pg_eventstore/cli.rb +42 -0
  29. data/lib/pg_eventstore/commands/read.rb +1 -1
  30. data/lib/pg_eventstore/extensions/options_extension.rb +53 -8
  31. data/lib/pg_eventstore/queries/event_queries.rb +1 -10
  32. data/lib/pg_eventstore/query_builders/events_filtering.rb +27 -0
  33. data/lib/pg_eventstore/rspec/has_option_matcher.rb +42 -41
  34. data/lib/pg_eventstore/stream.rb +8 -0
  35. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +13 -1
  36. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +1 -1
  37. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +1 -1
  38. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +3 -1
  39. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +10 -50
  40. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rb +22 -0
  41. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +1 -0
  42. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +7 -2
  43. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +44 -10
  44. data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +1 -1
  45. data/lib/pg_eventstore/utils.rb +32 -0
  46. data/lib/pg_eventstore/version.rb +1 -1
  47. data/lib/pg_eventstore/web/application.rb +12 -2
  48. data/lib/pg_eventstore/web/paginator/events_collection.rb +18 -5
  49. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +24 -2
  50. data/lib/pg_eventstore/web/views/home/dashboard.erb +9 -0
  51. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +1 -1
  52. data/lib/pg_eventstore/web/views/home/partials/events.erb +9 -6
  53. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +3 -3
  54. data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +15 -0
  55. data/lib/pg_eventstore/web/views/layouts/application.erb +3 -3
  56. data/lib/pg_eventstore/web/views/subscriptions/index.erb +6 -6
  57. data/lib/pg_eventstore.rb +5 -2
  58. data/sig/pg_eventstore/callbacks.rbs +4 -0
  59. data/sig/pg_eventstore/cli/commands/base_command.rbs +13 -0
  60. data/sig/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rbs +14 -0
  61. data/sig/pg_eventstore/cli/commands/help_command.rbs +13 -0
  62. data/sig/pg_eventstore/cli/commands/start_subscriptions_command.rbs +28 -0
  63. data/sig/pg_eventstore/cli/commands/stop_subscriptions_command.rbs +11 -0
  64. data/sig/pg_eventstore/cli/exit_codes.rbs +8 -0
  65. data/sig/pg_eventstore/cli/parser_options/base_options.rbs +15 -0
  66. data/sig/pg_eventstore/cli/parser_options/default_options.rbs +8 -0
  67. data/sig/pg_eventstore/cli/parser_options/metadata.rbs +11 -0
  68. data/sig/pg_eventstore/cli/parser_options/subscription_options.rbs +9 -0
  69. data/sig/pg_eventstore/cli/parsers/base_parser.rbs +18 -0
  70. data/sig/pg_eventstore/cli/parsers/default_parser.rbs +9 -0
  71. data/sig/pg_eventstore/cli/parsers/subscription_parser.rbs +9 -0
  72. data/sig/pg_eventstore/cli/try_to_delete_subscriptions_set.rbs +24 -0
  73. data/sig/pg_eventstore/cli/try_unlock_subscriptions_set.rbs +7 -0
  74. data/sig/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rbs +26 -0
  75. data/sig/pg_eventstore/cli.rbs +10 -0
  76. data/sig/pg_eventstore/extensions/options_extension.rbs +18 -1
  77. data/sig/pg_eventstore/queries/event_queries.rbs +0 -5
  78. data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +6 -0
  79. data/sig/pg_eventstore/stream.rbs +3 -0
  80. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rbs +8 -0
  81. data/sig/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rbs +7 -1
  82. data/sig/pg_eventstore/subscriptions/queries/subscription_command_queries.rbs +1 -1
  83. data/sig/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rbs +1 -1
  84. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +10 -11
  85. data/sig/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rbs +11 -0
  86. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +1 -1
  87. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +13 -1
  88. data/sig/pg_eventstore/utils.rbs +2 -0
  89. data/sig/pg_eventstore/web/application.rbs +27 -0
  90. data/sig/pg_eventstore/web/paginator/base_collection.rbs +0 -9
  91. data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +9 -0
  92. data/sig/pg_eventstore/web/paginator/events_collection.rbs +3 -1
  93. data/sig/pg_eventstore.rbs +2 -8
  94. metadata +47 -3
@@ -55,9 +55,11 @@ module PgEventstore
55
55
  conn.exec_params(sql, [command_name, subscriptions_set_id, data])
56
56
  end
57
57
  deserialize(pg_result.to_a.first)
58
+ rescue PG::ForeignKeyViolation
59
+ raise RecordNotFound.new("subscriptions_set", subscriptions_set_id)
58
60
  end
59
61
 
60
- # @param subscriptions_set_id [Integer]
62
+ # @param subscriptions_set_id [Integer, nil]
61
63
  # @return [Array<PgEventstore::SubscriptionFeederCommands::Base>]
62
64
  def find_commands(subscriptions_set_id)
63
65
  sql_builder =
@@ -6,37 +6,25 @@ module PgEventstore
6
6
  class SubscriptionFeeder
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async
10
- def_delegators :@subscriptions_lifecycle, :force_lock!
9
+ attr_reader :config_name
10
+
11
+ def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async, :running?
11
12
 
12
13
  # @param config_name [Symbol]
13
- # @param set_name [String]
14
- # @param max_retries [Integer] max number of retries of failed SubscriptionsSet
15
- # @param retries_interval [Integer] a delay between retries of failed SubscriptionsSet
16
- def initialize(config_name:, set_name:, max_retries:, retries_interval:)
14
+ # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
15
+ # @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
16
+ def initialize(config_name:, subscriptions_set_lifecycle:, subscriptions_lifecycle:)
17
17
  @config_name = config_name
18
18
  @basic_runner = BasicRunner.new(0.2, 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)
19
+ @subscriptions_set_lifecycle = subscriptions_set_lifecycle
20
+ @subscriptions_lifecycle = subscriptions_lifecycle
24
21
  @commands_handler = CommandsHandler.new(@config_name, self, @subscriptions_lifecycle.runners)
25
22
  attach_runner_callbacks
26
23
  end
27
24
 
28
- # @return [Integer]
25
+ # @return [Integer, nil]
29
26
  def id
30
- @subscriptions_set_lifecycle.persisted_subscriptions_set.id
31
- end
32
-
33
- # Adds SubscriptionRunner to the set
34
- # @param runner [PgEventstore::SubscriptionRunner]
35
- # @return [PgEventstore::SubscriptionRunner]
36
- def add(runner)
37
- assert_proper_state!
38
- @subscriptions_lifecycle.runners.push(runner)
39
- runner
27
+ @subscriptions_set_lifecycle.subscriptions_set&.id
40
28
  end
41
29
 
42
30
  # Starts all SubscriptionRunners. This is only available if SubscriptionFeeder's runner is alive.
@@ -57,20 +45,6 @@ module PgEventstore
57
45
  self
58
46
  end
59
47
 
60
- # Produces a copy of currently running Subscriptions. This is needed, because original Subscriptions objects are
61
- # dangerous to use - users may incidentally break their state.
62
- # @return [Array<PgEventstore::Subscription>]
63
- def read_only_subscriptions
64
- @subscriptions_lifecycle.subscriptions.map(&:dup)
65
- end
66
-
67
- # Produces a copy of current SubscriptionsSet. This is needed, because original SubscriptionsSet object is
68
- # dangerous to use - users may incidentally break its state.
69
- # @return [PgEventstore::SubscriptionsSet, nil]
70
- def read_only_subscriptions_set
71
- @subscriptions_set_lifecycle.subscriptions_set&.dup
72
- end
73
-
74
48
  private
75
49
 
76
50
  # @return [void]
@@ -133,19 +107,5 @@ module PgEventstore
133
107
  SubscriptionFeederHandlers.setup_handler(:update_subscriptions_set_restarts, @subscriptions_set_lifecycle)
134
108
  )
135
109
  end
136
-
137
- # This method helps to ensure that no Subscription is added after SubscriptionFeeder's runner is working
138
- # @return [void]
139
- # @raise [RuntimeError]
140
- def assert_proper_state!
141
- return if @basic_runner.initial? || @basic_runner.stopped?
142
- subscriptions_set = @subscriptions_set_lifecycle.persisted_subscriptions_set
143
-
144
- error_message = <<~TEXT
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.
147
- TEXT
148
- raise error_message
149
- end
150
110
  end
151
111
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Ping < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ queries(subscription_feeder.config_name).update(subscriptions_set_id, {})
11
+ end
12
+
13
+ private
14
+
15
+ # @param config_name [Symbol]
16
+ # @return [PgEventstore::SubscriptionsSetQueries]
17
+ def queries(config_name)
18
+ SubscriptionsSetQueries.new(PgEventstore.connection(config_name))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'subscription_feeder_commands/base'
4
+ require_relative 'subscription_feeder_commands/ping'
4
5
  require_relative 'subscription_feeder_commands/restore'
5
6
  require_relative 'subscription_feeder_commands/start_all'
6
7
  require_relative 'subscription_feeder_commands/stop'
@@ -13,12 +13,12 @@ module PgEventstore
13
13
 
14
14
  # @param config_name [Symbol]
15
15
  # @param subscriptions_set_lifecycle [PgEventstore::SubscriptionsSetLifecycle]
16
- def initialize(config_name, subscriptions_set_lifecycle)
16
+ def initialize(config_name, subscriptions_set_lifecycle, force_lock: false)
17
17
  @config_name = config_name
18
18
  @subscriptions_set_lifecycle = subscriptions_set_lifecycle
19
19
  @runners = []
20
20
  @subscriptions_pinged_at = Time.at(0)
21
- @force_lock = false
21
+ @force_lock = force_lock
22
22
  end
23
23
 
24
24
  # Locks all Subscriptions behind the current SubscriptionsSet
@@ -59,6 +59,11 @@ module PgEventstore
59
59
  # locked by the new SubscriptionsSet.
60
60
  # @return [void]
61
61
  def force_lock!
62
+ message = <<~TEXT
63
+ Usage of #force_lock! is deprecated and will be removed in v2. Please pass :force_lock keyword argument when \
64
+ initializing SubscriptionsManager. Example: PgEventstore.subscriptions_manager(force_lock: true)
65
+ TEXT
66
+ Utils.deprecation_warning(message)
62
67
  @force_lock = true
63
68
  end
64
69
 
@@ -36,20 +36,32 @@ module PgEventstore
36
36
  attr_reader :config
37
37
  private :config
38
38
 
39
- def_delegators :@subscription_feeder, :stop, :force_lock!
39
+ def_delegators :@subscription_feeder, :stop, :running?
40
+ def_delegators :@subscriptions_lifecycle, :force_lock!
40
41
 
41
42
  # @param config [PgEventstore::Config]
42
43
  # @param set_name [String]
43
44
  # @param max_retries [Integer, nil] max number of retries of failed SubscriptionsSet
44
45
  # @param retries_interval [Integer, nil] a delay between retries of failed SubscriptionsSet
45
- def initialize(config:, set_name:, max_retries: nil, retries_interval: nil)
46
+ # @param force_lock [Boolean] whether to force-lock subscriptions
47
+ def initialize(config:, set_name:, max_retries: nil, retries_interval: nil, force_lock: false)
46
48
  @config = config
47
49
  @set_name = set_name
50
+ @subscriptions_set_lifecycle = SubscriptionsSetLifecycle.new(
51
+ config_name,
52
+ {
53
+ name: set_name,
54
+ max_restarts_number: max_retries || config.subscriptions_set_max_retries,
55
+ time_between_restarts: retries_interval || config.subscriptions_set_retries_interval
56
+ }
57
+ )
58
+ @subscriptions_lifecycle = SubscriptionsLifecycle.new(
59
+ config_name, @subscriptions_set_lifecycle, force_lock: force_lock
60
+ )
48
61
  @subscription_feeder = SubscriptionFeeder.new(
49
- config_name: config.name,
50
- set_name: set_name,
51
- max_retries: max_retries || config.subscriptions_set_max_retries,
52
- retries_interval: retries_interval || config.subscriptions_set_retries_interval
62
+ config_name: config_name,
63
+ subscriptions_set_lifecycle: @subscriptions_set_lifecycle,
64
+ subscriptions_lifecycle: @subscriptions_lifecycle
53
65
  )
54
66
  end
55
67
 
@@ -97,30 +109,52 @@ module PgEventstore
97
109
  failed_subscription_notifier: failed_subscription_notifier
98
110
  )
99
111
 
100
- @subscription_feeder.add(runner)
112
+ @subscriptions_lifecycle.runners.push(runner)
101
113
  true
102
114
  end
103
115
 
104
116
  # @return [Array<PgEventstore::Subscription>]
105
117
  def subscriptions
106
- @subscription_feeder.read_only_subscriptions
118
+ @subscriptions_lifecycle.subscriptions.map(&:dup)
107
119
  end
108
120
 
109
121
  # @return [PgEventstore::SubscriptionsSet, nil]
110
122
  def subscriptions_set
111
- @subscription_feeder.read_only_subscriptions_set
123
+ @subscriptions_set_lifecycle.subscriptions_set&.dup
124
+ end
125
+
126
+ # @return [PgEventstore::BasicRunner]
127
+ # @raise [PgEventstore::SubscriptionAlreadyLockedError]
128
+ def start!
129
+ run_cli_callbacks do
130
+ @subscription_feeder.start
131
+ end
112
132
  end
113
133
 
114
134
  # @return [PgEventstore::BasicRunner, nil]
115
135
  def start
116
- @subscription_feeder.start
136
+ start!
117
137
  rescue PgEventstore::SubscriptionAlreadyLockedError => e
118
138
  PgEventstore.logger&.warn(e.message)
119
139
  nil
120
140
  end
121
141
 
142
+ # @return [Symbol]
143
+ def config_name
144
+ @config.name
145
+ end
146
+
122
147
  private
123
148
 
149
+ # @return [Object] the result of the passed block
150
+ def run_cli_callbacks
151
+ return yield unless defined?(::PgEventstore::CLI)
152
+
153
+ PgEventstore::CLI.callbacks.run_callbacks(:start_manager, self) do
154
+ yield
155
+ end
156
+ end
157
+
124
158
  # @param middlewares [Array<Symbol>, nil]
125
159
  # @param handler [#call]
126
160
  # @return [Proc]
@@ -27,7 +27,7 @@ module PgEventstore
27
27
 
28
28
  # @return [PgEventstore::SubscriptionsSet]
29
29
  def persisted_subscriptions_set
30
- @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(**@subscriptions_set_attrs)
30
+ @subscriptions_set ||= SubscriptionsSet.using_connection(@config_name).create(@subscriptions_set_attrs)
31
31
  end
32
32
 
33
33
  # @return [void]
@@ -69,6 +69,38 @@ module PgEventstore
69
69
  def original_global_position(raw_event)
70
70
  raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
71
71
  end
72
+
73
+ # @param message [String]
74
+ # @return [void]
75
+ def deprecation_warning(message)
76
+ PgEventstore.logger&.warn("\e[31m[DEPRECATED]: #{message}\e[0m")
77
+ end
78
+
79
+ # @param file_path [String]
80
+ # @param content [String]
81
+ # @return [void]
82
+ def write_to_file(file_path, content)
83
+ file = File.open(file_path, "w")
84
+ file.write(content)
85
+ file.close
86
+ end
87
+
88
+ # @param file_path [String]
89
+ # @return [void]
90
+ def remove_file(file_path)
91
+ File.delete(file_path)
92
+ rescue Errno::ENOENT
93
+ end
94
+
95
+ # @param file_path [String]
96
+ # @return [String, nil]
97
+ def read_pid(file_path)
98
+ file = File.open(file_path, "r")
99
+ file.readline.strip.tap do
100
+ file.close
101
+ end
102
+ rescue Errno::ENOENT
103
+ end
72
104
  end
73
105
  end
74
106
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = "1.5.0"
5
+ VERSION = "1.7.0"
6
6
  end
@@ -9,6 +9,7 @@ module PgEventstore
9
9
  set :environment, -> { (ENV['RACK_ENV'] || ENV['RAILS_ENV'] || ENV['APP_ENV'])&.to_sym || :development }
10
10
  set :logging, -> { environment == :development || environment == :test }
11
11
  set :erb, layout: :'layouts/application'
12
+ set :host_authorization, { allow_if: ->(_env) { true } }
12
13
 
13
14
  helpers(Paginator::Helpers, Subscriptions::Helpers) do
14
15
  # @return [Array<Hash>, nil]
@@ -19,6 +20,12 @@ module PgEventstore
19
20
  end&.reject { _1.empty? }
20
21
  end
21
22
 
23
+ # @return [String, nil]
24
+ def system_stream
25
+ params in { filter: { system_stream: String => system_stream } }
26
+ system_stream if Stream::KNOWN_SYSTEM_STREAMS.include?(system_stream)
27
+ end
28
+
22
29
  # @return [Array<String>, nil]
23
30
  def events_filter
24
31
  params in { filter: { events: Array => events } }
@@ -31,6 +38,8 @@ module PgEventstore
31
38
  PgEventstore.available_configs.include?(config) ? config : :default
32
39
  end
33
40
 
41
+ # @param val [Object]
42
+ # @return [void]
34
43
  def current_config=(val)
35
44
  response.set_cookie('current_config', { value: val.to_s, http_only: true, same_site: :lax })
36
45
  end
@@ -40,7 +49,7 @@ module PgEventstore
40
49
  PgEventstore.connection(current_config)
41
50
  end
42
51
 
43
- # @param collection [PgEventstore::Paginator::BaseCollection]
52
+ # @param collection [PgEventstore::Web::Paginator::BaseCollection]
44
53
  # @return [void]
45
54
  def paginated_json_response(collection)
46
55
  halt 200, {
@@ -85,7 +94,8 @@ module PgEventstore
85
94
  options: {
86
95
  filter: { event_types: events_filter, streams: streams_filter },
87
96
  resolve_link_tos: resolve_link_tos?
88
- }
97
+ },
98
+ system_stream: system_stream
89
99
  )
90
100
 
91
101
  if request.xhr?
@@ -19,10 +19,21 @@ module PgEventstore
19
19
  # count instead because of the potential performance degradation.
20
20
  MAX_NUMBER_TO_COUNT = 10_000
21
21
 
22
+ # @param config_name [Symbol]
23
+ # @param starting_id [String, Integer, nil]
24
+ # @param per_page [Integer]
25
+ # @param order [Symbol] :asc or :desc
26
+ # @param options [Hash] additional options to filter the collection
27
+ # @param system_stream [String, nil] a name of system stream
28
+ def initialize(config_name, starting_id:, per_page:, order:, options: {}, system_stream: nil)
29
+ super(config_name, starting_id: starting_id, per_page: per_page, order: order, options: options)
30
+ @stream = system_stream ? PgEventstore::Stream.system_stream(system_stream) : PgEventstore::Stream.all_stream
31
+ end
32
+
22
33
  # @return [Array<PgEventstore::Event>]
23
34
  def collection
24
35
  @_collection ||= PgEventstore.client(config_name).read(
25
- PgEventstore::Stream.all_stream,
36
+ @stream,
26
37
  options: options.merge(from_position: starting_id, max_count: per_page, direction: order),
27
38
  middlewares: []
28
39
  )
@@ -33,7 +44,8 @@ module PgEventstore
33
44
  return unless collection.size == per_page
34
45
 
35
46
  from_position = event_global_position(collection.first)
36
- sql_builder = QueryBuilders::EventsFiltering.all_stream_filtering(
47
+ sql_builder = QueryBuilders::EventsFiltering.events_filtering(
48
+ @stream,
37
49
  options.merge(from_position: from_position, max_count: 1, direction: order)
38
50
  ).to_sql_builder.unselect.select('global_position').offset(per_page)
39
51
  global_position(sql_builder)
@@ -42,7 +54,8 @@ module PgEventstore
42
54
  # @return [Integer, nil]
43
55
  def prev_page_starting_id
44
56
  from_position = event_global_position(collection.first) || starting_id
45
- sql_builder = QueryBuilders::EventsFiltering.all_stream_filtering(
57
+ sql_builder = QueryBuilders::EventsFiltering.events_filtering(
58
+ @stream,
46
59
  options.merge(from_position: from_position, max_count: per_page, direction: order == :asc ? :desc : :asc)
47
60
  ).to_sql_builder.unselect.select('global_position').offset(1)
48
61
  sql, params = sql_builder.to_exec_params
@@ -57,7 +70,7 @@ module PgEventstore
57
70
  @_total_count ||=
58
71
  begin
59
72
  sql_builder =
60
- QueryBuilders::EventsFiltering.all_stream_filtering(options).
73
+ QueryBuilders::EventsFiltering.events_filtering(@stream, options).
61
74
  to_sql_builder.remove_limit.remove_group.remove_order
62
75
  count = estimate_count(sql_builder)
63
76
  return count if count > MAX_NUMBER_TO_COUNT
@@ -68,7 +81,7 @@ module PgEventstore
68
81
 
69
82
  private
70
83
 
71
- # @param event [PgEventstore::Event]
84
+ # @param event [PgEventstore::Event, nil]
72
85
  # @return [Integer, nil]
73
86
  def event_global_position(event)
74
87
  event&.link&.global_position || event&.global_position
@@ -60,6 +60,13 @@ $(function(){
60
60
  });
61
61
  }
62
62
 
63
+ let initSystemStreamFilterAutocomplete = function($filter){
64
+ let $streamNameSelect = $filter.find('select');
65
+ $streamNameSelect.select2({
66
+ allowClear: true
67
+ });
68
+ }
69
+
63
70
  let initEventTypeFilterAutocomplete = function($filter) {
64
71
  let $eventTypeSelect = $filter.find('select');
65
72
  $eventTypeSelect.select2({
@@ -86,6 +93,8 @@ $(function(){
86
93
  let $filtersForm = $('#filters-form');
87
94
  // Stream filter template
88
95
  let streamFilterTmpl = $('#stream-filter-tmpl').text();
96
+ // System stream filter template
97
+ let systemStreamFilterTmpl = $('#system-stream-filter-tmpl').text();
89
98
  // Event type filter template
90
99
  let eventFilterTmpl = $('#event-type-filter-tmpl').text();
91
100
 
@@ -93,21 +102,34 @@ $(function(){
93
102
  $filtersForm.on('click', '.remove-filter', function(){
94
103
  $(this).parents('.form-row').remove();
95
104
  });
96
- // Add stream filter button
105
+ // "Add stream filter" button
97
106
  $filtersForm.on('click', '.add-stream-filter', function(){
98
107
  let filtersNum = $filtersForm.find('.stream-filters').children().length + '';
99
108
  $filtersForm.find('.stream-filters').append(streamFilterTmpl.replace(/%NUM%/g, filtersNum));
100
109
  initStreamFilterAutocomplete($filtersForm.find('.stream-filters').children().last());
101
110
  });
102
- // Add event type filter button
111
+ // "Add system stream filter" button
112
+ $filtersForm.on('click', '.add-system-stream-filter', function(){
113
+ if($filtersForm.find('.system-stream-filter').children().length > 0)
114
+ return;
115
+
116
+ $filtersForm.find('.system-stream-filter').append(systemStreamFilterTmpl);
117
+ initSystemStreamFilterAutocomplete($filtersForm.find('.system-stream-filter').children().last());
118
+ });
119
+ // "Add event type filter" button
103
120
  $filtersForm.on('click', '.add-event-filter', function(){
104
121
  $filtersForm.find('.event-filters').append(eventFilterTmpl);
105
122
  initEventTypeFilterAutocomplete($filtersForm.find('.event-filters').children().last());
106
123
  });
124
+
107
125
  // Init select2 for stream filters which were initially rendered
108
126
  $filtersForm.find('.stream-filters').children().each(function(){
109
127
  initStreamFilterAutocomplete($(this));
110
128
  });
129
+ // Init select2 for system stream filter which were initially rendered
130
+ $filtersForm.find('.system-stream-filter').children().each(function(){
131
+ initSystemStreamFilterAutocomplete($(this));
132
+ });
111
133
  // Init select2 for event type filters which were initially rendered
112
134
  $filtersForm.find('.event-filters').children().each(function(){
113
135
  initEventTypeFilterAutocomplete($(this));
@@ -1,6 +1,9 @@
1
1
  <script type="text/html" id="stream-filter-tmpl">
2
2
  <%= erb :'home/partials/stream_filter', { layout: false }, { stream: {} } %>
3
3
  </script>
4
+ <script type="text/html" id="system-stream-filter-tmpl">
5
+ <%= erb :'home/partials/system_stream_filter', { layout: false }, { stream: nil } %>
6
+ </script>
4
7
  <script type="text/html" id="event-type-filter-tmpl">
5
8
  <%= erb :'home/partials/event_filter', { layout: false }, { event_type: '' } %>
6
9
  </script>
@@ -27,6 +30,11 @@
27
30
  <form id="filters-form" action="<%= url('/') %>" method="GET" data-parsley-validate class="form-horizontal form-label-left">
28
31
  <input type="hidden" name="per_page" value="<%= params[:per_page].to_i %>">
29
32
  <input type="hidden" name="resolve_link_tos" value="<%= resolve_link_tos? %>">
33
+ <div class="system-stream-filter">
34
+ <% if system_stream %>
35
+ <%= erb :'home/partials/system_stream_filter', { layout: false }, { stream: system_stream } %>
36
+ <% end %>
37
+ </div>
30
38
  <div class="stream-filters">
31
39
  <% streams_filter&.each do |attrs| %>
32
40
  <%= erb :'home/partials/stream_filter', { layout: false }, { stream: attrs } %>
@@ -48,6 +56,7 @@
48
56
  </button>
49
57
  <div class="dropdown-menu">
50
58
  <a class="dropdown-item add-stream-filter" href="javascript: void(0)">Add stream filter</a>
59
+ <a class="dropdown-item add-system-stream-filter" href="javascript: void(0)">Add system stream filter</a>
51
60
  <a class="dropdown-item add-event-filter" href="javascript: void(0)">Add event filter</a>
52
61
  </div>
53
62
  </div>
@@ -3,7 +3,7 @@
3
3
  <select name="filter[events][]" class="form-control mb-2" data-placeholder="Event type" data-url="<%= url('/event_types_filtering') %>">
4
4
  <option></option>
5
5
  <% if event_type %>
6
- <option value="<%= event_type %>" selected><%= event_type %></option>
6
+ <option value="<%= h event_type %>" selected><%= h event_type %></option>
7
7
  <% end %>
8
8
  </select>
9
9
  </div>
@@ -2,14 +2,17 @@
2
2
  <tr>
3
3
  <td><%= event.global_position %></td>
4
4
  <td><%= event.stream_revision %></td>
5
- <td><%= event.stream.context %></td>
6
- <td><%= event.stream.stream_name %></td>
5
+ <td><%= h event.stream.context %></td>
6
+ <td><%= h event.stream.stream_name %></td>
7
7
  <td><a href="<%= stream_path(event) %>"><%= event.stream.stream_id %></a></td>
8
8
  <td>
9
- <%= event.type %>
9
+ <p class="float-left"><%= h event.type %></p>
10
10
  <% if event.link %>
11
- <i class="fa fa-link"></i>
11
+ <p class="float-left ml-2">
12
+ <i class="fa fa-link"></i>
13
+ </p>
12
14
  <% end %>
15
+ <div class="clearfix"></div>
13
16
  </td>
14
17
  <td><%= event.created_at.strftime('%F %T') %></td>
15
18
  <td><%= event.id %></td>
@@ -19,9 +22,9 @@
19
22
  <tr class="event-payload d-none">
20
23
  <td colspan="8">
21
24
  <strong>Data:</strong>
22
- <pre><%= JSON.pretty_generate(event.data) %></pre>
25
+ <pre><%= h JSON.pretty_generate(event.data) %></pre>
23
26
  <strong>Metadata:</strong>
24
- <pre><%= JSON.pretty_generate(event.metadata) %></pre>
27
+ <pre><%= h JSON.pretty_generate(event.metadata) %></pre>
25
28
  </td>
26
29
  </tr>
27
30
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <select name="filter[streams][][context]" class="form-control mb-2" data-placeholder="Context" data-url="<%= url('/stream_contexts_filtering') %>">
4
4
  <option></option>
5
5
  <% if stream[:context] %>
6
- <option value="<%= stream[:context] %>" selected><%= stream[:context] %></option>
6
+ <option value="<%= h stream[:context] %>" selected><%= h stream[:context] %></option>
7
7
  <% end %>
8
8
  </select>
9
9
  </div>
@@ -11,7 +11,7 @@
11
11
  <select name="filter[streams][][stream_name]" class="form-control mb-2" data-placeholder="Stream name" data-url="<%= url('/stream_names_filtering') %>">
12
12
  <option></option>
13
13
  <% if stream[:stream_name] %>
14
- <option value="<%= stream[:stream_name] %>" selected><%= stream[:stream_name] %></option>
14
+ <option value="<%= h stream[:stream_name] %>" selected><%= h stream[:stream_name] %></option>
15
15
  <% end %>
16
16
  </select>
17
17
  </div>
@@ -19,7 +19,7 @@
19
19
  <select name="filter[streams][][stream_id]" class="form-control mb-2" data-placeholder="Stream ID" data-url="<%= url('/stream_ids_filtering') %>">
20
20
  <option></option>
21
21
  <% if stream[:stream_id] %>
22
- <option value="<%= stream[:stream_id] %>" selected><%= stream[:stream_id] %></option>
22
+ <option value="<%= h stream[:stream_id] %>" selected><%= h stream[:stream_id] %></option>
23
23
  <% end %>
24
24
  </select>
25
25
  </div>
@@ -0,0 +1,15 @@
1
+ <div class="form-row align-items-center">
2
+ <div class="col-3">
3
+ <select name="filter[system_stream]" class="form-control mb-2" data-placeholder="Select system stream">
4
+ <option></option>
5
+ <% PgEventstore::Stream::KNOWN_SYSTEM_STREAMS.each do |stream_name| %>
6
+ <option value="<%= stream_name %>" <% if stream == stream_name %> selected <% end %>><%= stream_name %></option>
7
+ <% end %>
8
+ </select>
9
+ </div>
10
+ <div class="col-1">
11
+ <a class="btn btn-default remove-filter" href="javascript: void(0);">
12
+ <i class="fa fa-minus-circle"></i>
13
+ </a>
14
+ </div>
15
+ </div>
@@ -82,13 +82,13 @@
82
82
  <ul class="navbar-right" style="height: 29px;">
83
83
  <li class="nav-item dropdown open" style="padding-left: 15px;">
84
84
  <a href="javascript: void(0);" class="user-profile dropdown-toggle" aria-haspopup="true" id="navbarDropdown" data-toggle="dropdown" aria-expanded="false">
85
- <strong>Current config:</strong> <%= current_config.inspect %>
85
+ <strong>Current config:</strong> <%= h current_config.inspect %>
86
86
  </a>
87
87
  <div class="dropdown-menu dropdown-usermenu pull-right" aria-labelledby="navbarDropdown">
88
88
  <% PgEventstore.available_configs.each do |config| %>
89
89
  <form action="<%= url('/change_config') %>" method="POST">
90
- <input type="hidden" name="config" value="<%= config %>">
91
- <button type="submit" class="dropdown-item"><%= config.inspect %></button>
90
+ <input type="hidden" name="config" value="<%= h config.to_s %>">
91
+ <button type="submit" class="dropdown-item"><%= h config.inspect %></button>
92
92
  </form>
93
93
  <% end %>
94
94
  </div>