pg_eventstore 1.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +45 -0
- data/db/migrations/7_support_reading_streams_system_stream.sql +2 -0
- data/docs/reading_events.md +16 -1
- data/lib/pg_eventstore/commands/read.rb +1 -1
- data/lib/pg_eventstore/queries/event_queries.rb +1 -10
- data/lib/pg_eventstore/query_builders/events_filtering.rb +27 -0
- data/lib/pg_eventstore/rspec/has_option_matcher.rb +4 -28
- data/lib/pg_eventstore/stream.rb +8 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +12 -2
- data/lib/pg_eventstore/web/paginator/events_collection.rb +18 -5
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +24 -2
- data/lib/pg_eventstore/web/views/home/dashboard.erb +9 -0
- data/lib/pg_eventstore/web/views/home/partials/events.erb +5 -2
- data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +15 -0
- data/lib/pg_eventstore/web/views/layouts/application.erb +1 -1
- data/sig/pg_eventstore/queries/event_queries.rbs +0 -5
- data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +6 -0
- data/sig/pg_eventstore/stream.rbs +3 -0
- data/sig/pg_eventstore/web/application.rbs +27 -0
- data/sig/pg_eventstore/web/paginator/base_collection.rbs +0 -9
- data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +9 -0
- data/sig/pg_eventstore/web/paginator/events_collection.rbs +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff514958069cbf4b819b2ad959eac80ca9f24445bca19690ed4cb3ff23989804
|
4
|
+
data.tar.gz: bd91be800c3739a43edf4c17c996156642f00f0d977446201c84784a1bcc6e74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d4946c58c314f0bfb04c0c20ca1308e4c8afe915adacee8722fcd703404b26dbc429bdda5bc5a44f94654cb366813065226b42c930620bf30fcd4a345f592b1
|
7
|
+
data.tar.gz: 89738f55a626cbcd4eab9d336b7f4b9ff2e3520fc30a7fc7d87dfac5e755cd6431392438d1522cec706188aa855942db4cfd6fa96bc83c5e0b67086d6e05cef8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.7.0]
|
4
|
+
- Implement reading from `"$streams"` system stream
|
5
|
+
- Disable Host authorization introduced in sinatra v4.1
|
6
|
+
|
3
7
|
## [1.6.0]
|
4
8
|
- Introduce subscriptions CLI. Type `pg-eventstore subscriptions --help` to see available commands. The main purpose of it is to provide the single way to start/stop subscription processes. Check [Subscriptions](docs/subscriptions.md#creating-a-subscription) docs about the new way to start and keep running a subscriptions process.
|
5
9
|
|
data/README.md
CHANGED
@@ -53,6 +53,51 @@ Documentation chapters:
|
|
53
53
|
|
54
54
|
The gem is shipped with its own CLI. Use `pg-eventstore --help` to find out its capabilities.
|
55
55
|
|
56
|
+
## RSpec
|
57
|
+
|
58
|
+
### Clean up test db
|
59
|
+
|
60
|
+
The gem provides a class to clean up your `pg_eventstore` test db between tests. Example usage(in your `spec/spec_helper.rb`:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
require 'pg_eventstore/rspec/test_helpers'
|
64
|
+
|
65
|
+
RSpec.configure do |config|
|
66
|
+
config.before do
|
67
|
+
PgEventstore::TestHelpers.clean_up_db
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
### RSpec matcher for OptionsExtension
|
73
|
+
|
74
|
+
If you would like to be able to test the functional, provided by `PgEventstore::Extensions::OptionsExtension` extension - there is a rspec matcher. Load custom matcher in you `spec_helper.rb`:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'pg_eventstore/rspec/has_option_matcher'
|
78
|
+
```
|
79
|
+
|
80
|
+
Let's say you have next class:
|
81
|
+
```ruby
|
82
|
+
class SomeClass
|
83
|
+
include PgEventstore::Extensions::OptionsExtension
|
84
|
+
|
85
|
+
option(:some_opt, metadata: { foo: :bar }) { '1' }
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
To test that its instance has the proper option with the proper default value and proper metadata you can use this matcher:
|
90
|
+
```ruby
|
91
|
+
RSpec.describe SomeClass do
|
92
|
+
subject { described_class.new }
|
93
|
+
|
94
|
+
# Check that :some_opt is present
|
95
|
+
it { is_expected.to have_option(:some_opt) }
|
96
|
+
# Check that :some_opt is present and has the correct default value
|
97
|
+
it { is_expected.to have_option(:some_opt).with_default_value('1').with_metadata(foo: :bar) }
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
56
101
|
## Development
|
57
102
|
|
58
103
|
After checking out the repo, run:
|
data/docs/reading_events.md
CHANGED
@@ -53,7 +53,7 @@ In case a stream with given name does not exist, a `PgEventstore::StreamNotFound
|
|
53
53
|
```ruby
|
54
54
|
begin
|
55
55
|
stream = PgEventstore::Stream.new(context: 'non-existing-context', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
|
56
|
-
PgEventstore.client.read(stream)
|
56
|
+
PgEventstore.client.read(stream, options: { max_count: 1 })
|
57
57
|
rescue PgEventstore::StreamNotFoundError => e
|
58
58
|
puts e.message # => Stream #<PgEventstore::Stream:0x01> does not exist.
|
59
59
|
puts e.stream # => #<PgEventstore::Stream:0x01>
|
@@ -80,6 +80,15 @@ You can read from a specific position of the "all" stream. This is very similar
|
|
80
80
|
PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { from_position: 9023, direction: 'Backwards' })
|
81
81
|
```
|
82
82
|
|
83
|
+
## Reading from "$streams" system stream
|
84
|
+
|
85
|
+
`"$streams"` is a special stream which consists of events with `stream_revision == 0`. This allows you to effectively query all streams. Example:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
stream = PgEventstore::Stream.system_stream("$streams")
|
89
|
+
PgEventstore.client.read(stream).map(&:stream) # => array of unique streams
|
90
|
+
```
|
91
|
+
|
83
92
|
## Middlewares
|
84
93
|
|
85
94
|
If you would like to skip some of your registered middlewares from processing events after they being read from a stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like to use.
|
@@ -160,6 +169,12 @@ You can also mix filtering by stream's attributes and event types. The result wi
|
|
160
169
|
PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
|
161
170
|
```
|
162
171
|
|
172
|
+
### "$streams" stream filtering
|
173
|
+
|
174
|
+
When reading from `"$streams"` same rules apply as when reading from "all" stream. For example, read all streams which have `context == "MyAwesomeContext"` and start from events with event type either `"Foo"` or `"Bar"`:
|
175
|
+
```ruby
|
176
|
+
PgEventstore.client.read(PgEventstore::Stream.system_stream("$streams"), options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
|
177
|
+
```
|
163
178
|
|
164
179
|
## Pagination
|
165
180
|
|
@@ -15,7 +15,7 @@ module PgEventstore
|
|
15
15
|
# @return [Array<PgEventstore::Event>]
|
16
16
|
# @raise [PgEventstore::StreamNotFoundError]
|
17
17
|
def call(stream, options: {})
|
18
|
-
queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.
|
18
|
+
queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.system?
|
19
19
|
|
20
20
|
queries.events.stream_events(stream, options)
|
21
21
|
end
|
@@ -70,7 +70,7 @@ module PgEventstore
|
|
70
70
|
# @param options [Hash]
|
71
71
|
# @return [Array<PgEventstore::Event>]
|
72
72
|
def stream_events(stream, options)
|
73
|
-
exec_params = events_filtering(stream, options).to_exec_params
|
73
|
+
exec_params = QueryBuilders::EventsFiltering.events_filtering(stream, options).to_exec_params
|
74
74
|
raw_events = connection.with do |conn|
|
75
75
|
conn.exec_params(*exec_params)
|
76
76
|
end.to_a
|
@@ -128,15 +128,6 @@ module PgEventstore
|
|
128
128
|
[sql_rows_for_insert, values]
|
129
129
|
end
|
130
130
|
|
131
|
-
# @param stream [PgEventstore::Stream]
|
132
|
-
# @param options [Hash]
|
133
|
-
# @return [PgEventstore::EventsFiltering]
|
134
|
-
def events_filtering(stream, options)
|
135
|
-
return QueryBuilders::EventsFiltering.all_stream_filtering(options) if stream.all_stream?
|
136
|
-
|
137
|
-
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options)
|
138
|
-
end
|
139
|
-
|
140
131
|
# @return [PgEventstore::LinksResolver]
|
141
132
|
def links_resolver
|
142
133
|
LinksResolver.new(connection)
|
@@ -21,6 +21,18 @@ module PgEventstore
|
|
21
21
|
SUBSCRIPTIONS_OPTIONS = %i[from_position resolve_link_tos filter max_count].freeze
|
22
22
|
|
23
23
|
class << self
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [PgEventstore::EventsFiltering]
|
27
|
+
def events_filtering(stream, options)
|
28
|
+
return all_stream_filtering(options) if stream.all_stream?
|
29
|
+
if stream.system? && Stream::KNOWN_SYSTEM_STREAMS.include?(stream.context)
|
30
|
+
return system_stream_filtering(stream, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
specific_stream_filtering(stream, options)
|
34
|
+
end
|
35
|
+
|
24
36
|
# @param options [Hash]
|
25
37
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
26
38
|
def subscriptions_events_filtering(options)
|
@@ -54,6 +66,15 @@ module PgEventstore
|
|
54
66
|
event_filter.add_stream_direction(options[:direction])
|
55
67
|
event_filter
|
56
68
|
end
|
69
|
+
|
70
|
+
# @param stream [PgEventstore::Stream] system stream
|
71
|
+
# @param options [Hash]
|
72
|
+
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
73
|
+
def system_stream_filtering(stream, options)
|
74
|
+
all_stream_filtering(options).tap do |event_filter|
|
75
|
+
event_filter.set_source(stream.context)
|
76
|
+
end
|
77
|
+
end
|
57
78
|
end
|
58
79
|
|
59
80
|
def initialize
|
@@ -139,6 +160,12 @@ module PgEventstore
|
|
139
160
|
@sql_builder.to_exec_params
|
140
161
|
end
|
141
162
|
|
163
|
+
# @param table_name [String] system stream view name
|
164
|
+
# @return [void]
|
165
|
+
def set_source(table_name)
|
166
|
+
@sql_builder.from(%{ "#{PG::Connection.escape(table_name)}" events })
|
167
|
+
end
|
168
|
+
|
142
169
|
private
|
143
170
|
|
144
171
|
# @param stream_attrs [Hash]
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# This matcher is defined to test options which are defined by using
|
4
|
-
#
|
4
|
+
# PgEventstore::Extensions::OptionsExtension option. Example:
|
5
5
|
# Let's say you have next class
|
6
6
|
# class SomeClass
|
7
7
|
# include PgEventstore::Extensions::OptionsExtension
|
8
8
|
#
|
9
|
-
# option(:some_opt) { '1' }
|
9
|
+
# option(:some_opt, metadata: { foo: :bar }) { '1' }
|
10
10
|
# end
|
11
11
|
#
|
12
|
-
# To test that its instance has the proper option with the proper default value you can use this
|
12
|
+
# To test that its instance has the proper option with the proper default value and proper metadata you can use this
|
13
13
|
# matcher:
|
14
14
|
# RSpec.describe SomeClass do
|
15
15
|
# subject { described_class.new }
|
@@ -17,31 +17,7 @@
|
|
17
17
|
# # Check that :some_opt is present
|
18
18
|
# it { is_expected.to have_option(:some_opt) }
|
19
19
|
# # Check that :some_opt is present and has the correct default value
|
20
|
-
# it { is_expected.to have_option(:some_opt).with_default_value('1') }
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# If you have more complex implementation of default value of your option - you should handle it
|
24
|
-
# customly. For example:
|
25
|
-
# class SomeClass
|
26
|
-
# include PgEventstore::Extensions::OptionsExtension
|
27
|
-
#
|
28
|
-
# option(:some_opt) { calc_value }
|
29
|
-
# end
|
30
|
-
# You could test it like so:
|
31
|
-
# RSpec.described SomeClass do
|
32
|
-
# let(:instance) { described_class.new }
|
33
|
-
#
|
34
|
-
# describe ':some_opt default value' do
|
35
|
-
# subject { instance.some_opt }
|
36
|
-
#
|
37
|
-
# let(:value) { 'some val' }
|
38
|
-
#
|
39
|
-
# before do
|
40
|
-
# allow(instance).to receive(:calc_value).and_return(value)
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# it { is_expected.to eq(value) }
|
44
|
-
# end
|
20
|
+
# it { is_expected.to have_option(:some_opt).with_default_value('1').with_metadata(foo: :bar) }
|
45
21
|
# end
|
46
22
|
RSpec::Matchers.define :has_option do |option_name|
|
47
23
|
match do |obj|
|
data/lib/pg_eventstore/stream.rb
CHANGED
@@ -8,6 +8,8 @@ module PgEventstore
|
|
8
8
|
SYSTEM_STREAM_PREFIX = '$'
|
9
9
|
# @return [Integer]
|
10
10
|
NON_EXISTING_STREAM_REVISION = -1
|
11
|
+
# @return [Array<String>]
|
12
|
+
KNOWN_SYSTEM_STREAMS = %w[$streams].freeze
|
11
13
|
|
12
14
|
class << self
|
13
15
|
# Produces "all" stream instance. "all" stream does not represent any specific stream. Instead, it indicates that
|
@@ -18,6 +20,12 @@ module PgEventstore
|
|
18
20
|
stream.instance_variable_set(:@all_stream, true)
|
19
21
|
end
|
20
22
|
end
|
23
|
+
|
24
|
+
# @param name [String]
|
25
|
+
# @return [PgEventstore::Stream]
|
26
|
+
def system_stream(name)
|
27
|
+
new(context: name, stream_name: '', stream_id: '')
|
28
|
+
end
|
21
29
|
end
|
22
30
|
|
23
31
|
# @!attribute context
|
@@ -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
|
-
|
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.
|
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.
|
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.
|
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
|
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>
|
@@ -6,10 +6,13 @@
|
|
6
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
|
-
|
9
|
+
<p class="float-left"><%= h event.type %></p>
|
10
10
|
<% if event.link %>
|
11
|
-
<
|
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>
|
@@ -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>
|
@@ -87,7 +87,7 @@
|
|
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="<%= h config %>">
|
90
|
+
<input type="hidden" name="config" value="<%= h config.to_s %>">
|
91
91
|
<button type="submit" class="dropdown-item"><%= h config.inspect %></button>
|
92
92
|
</form>
|
93
93
|
<% end %>
|
@@ -31,11 +31,6 @@ module PgEventstore
|
|
31
31
|
# _@param_ `events`
|
32
32
|
def prepared_statements: (PgEventstore::Stream stream, ::Array[PgEventstore::Event] events) -> ::Array[(::Array[String] | ::Array[Object])]
|
33
33
|
|
34
|
-
# _@param_ `stream`
|
35
|
-
#
|
36
|
-
# _@param_ `options`
|
37
|
-
def events_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
|
38
|
-
|
39
34
|
def links_resolver: () -> PgEventstore::LinksResolver
|
40
35
|
|
41
36
|
# Returns the value of attribute connection.
|
@@ -6,6 +6,10 @@ module PgEventstore
|
|
6
6
|
SQL_DIRECTIONS: Hash[String | Symbol, String]
|
7
7
|
SUBSCRIPTIONS_OPTIONS: ::Array[Symbol]
|
8
8
|
|
9
|
+
@sql_builder: PgEventstore::SQLBuilder
|
10
|
+
|
11
|
+
def self.events_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
|
12
|
+
|
9
13
|
# _@param_ `options`
|
10
14
|
def self.subscriptions_events_filtering: (::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
|
11
15
|
|
@@ -17,6 +21,8 @@ module PgEventstore
|
|
17
21
|
# _@param_ `options`
|
18
22
|
def self.specific_stream_filtering: (PgEventstore::Stream stream, ::Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
|
19
23
|
|
24
|
+
def self.system_stream_filtering: (PgEventstore::Stream stream, Hash[untyped, untyped] options) -> PgEventstore::QueryBuilders::EventsFiltering
|
25
|
+
|
20
26
|
def initialize: () -> void
|
21
27
|
|
22
28
|
# _@param_ `context`
|
@@ -1,10 +1,13 @@
|
|
1
1
|
module PgEventstore
|
2
2
|
class Stream
|
3
|
+
KNOWN_SYSTEM_STREAMS: Array[String]
|
3
4
|
SYSTEM_STREAM_PREFIX: String
|
4
5
|
NON_EXISTING_STREAM_REVISION: Integer
|
5
6
|
|
6
7
|
def self.all_stream: () -> PgEventstore::Stream
|
7
8
|
|
9
|
+
def self.system_stream: (String) -> PgEventstore::Stream
|
10
|
+
|
8
11
|
# _@param_ `context`
|
9
12
|
#
|
10
13
|
# _@param_ `stream_name`
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PgEventstore
|
2
|
+
module Web
|
3
|
+
class Application
|
4
|
+
def asset_url: (String path) -> String
|
5
|
+
|
6
|
+
def connection: -> PgEventstore::Connection
|
7
|
+
|
8
|
+
def current_config: -> Symbol
|
9
|
+
|
10
|
+
def current_config=: (untyped val) -> void
|
11
|
+
|
12
|
+
def events_filter: -> Array[String]?
|
13
|
+
|
14
|
+
def h: (String text) -> String
|
15
|
+
|
16
|
+
def paginated_json_response: (PgEventstore::Web::Paginator::BaseCollection collection) -> void
|
17
|
+
|
18
|
+
def redirect_back_url: (fallback_url: String) -> String
|
19
|
+
|
20
|
+
def resolve_link_tos?: -> bool
|
21
|
+
|
22
|
+
def streams_filter: -> Array[Hash[untyped, untyped]]?
|
23
|
+
|
24
|
+
def system_stream: -> String?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,15 +2,6 @@ module PgEventstore
|
|
2
2
|
module Web
|
3
3
|
module Paginator
|
4
4
|
class BaseCollection
|
5
|
-
# _@param_ `config_name`
|
6
|
-
#
|
7
|
-
# _@param_ `starting_id`
|
8
|
-
#
|
9
|
-
# _@param_ `per_page`
|
10
|
-
#
|
11
|
-
# _@param_ `order` — :asc or :desc
|
12
|
-
#
|
13
|
-
# _@param_ `options` — additional options to filter the collection
|
14
5
|
def initialize: (
|
15
6
|
Symbol config_name,
|
16
7
|
starting_id: (String | Integer)?,
|
@@ -4,6 +4,15 @@ module PgEventstore
|
|
4
4
|
class EventTypesCollection < PgEventstore::Web::Paginator::BaseCollection
|
5
5
|
PER_PAGE: Integer
|
6
6
|
|
7
|
+
def initialize: (
|
8
|
+
Symbol config_name,
|
9
|
+
starting_id: (String | Integer)?,
|
10
|
+
per_page: Integer,
|
11
|
+
order: Symbol,
|
12
|
+
?options: ::Hash[untyped, untyped],
|
13
|
+
?system_stream: String?
|
14
|
+
) -> void
|
15
|
+
|
7
16
|
def collection: () -> ::Array[::Hash[String, String]]
|
8
17
|
|
9
18
|
def next_page_starting_id: () -> String?
|
@@ -6,6 +6,8 @@ module PgEventstore
|
|
6
6
|
PER_PAGE: ::Hash[String, Integer]
|
7
7
|
MAX_NUMBER_TO_COUNT: Integer
|
8
8
|
|
9
|
+
@stream: PgEventstore::Stream
|
10
|
+
|
9
11
|
def collection: () -> ::Array[PgEventstore::Event]
|
10
12
|
|
11
13
|
def next_page_starting_id: () -> Integer?
|
@@ -15,7 +17,7 @@ module PgEventstore
|
|
15
17
|
def total_count: () -> Integer
|
16
18
|
|
17
19
|
# _@param_ `event`
|
18
|
-
def event_global_position: (PgEventstore::Event event) -> Integer?
|
20
|
+
def event_global_position: (PgEventstore::Event? event) -> Integer?
|
19
21
|
|
20
22
|
# _@param_ `sql_builder`
|
21
23
|
def estimate_count: (PgEventstore::SQLBuilder sql_builder) -> Integer
|
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.
|
4
|
+
version: 1.7.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:
|
11
|
+
date: 2025-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -77,6 +77,7 @@ files:
|
|
77
77
|
- db/migrations/4_create_subscriptions_set_commands.sql
|
78
78
|
- db/migrations/5_partitions.sql
|
79
79
|
- db/migrations/6_add_commands_data.sql
|
80
|
+
- db/migrations/7_support_reading_streams_system_stream.sql
|
80
81
|
- docs/admin_ui.md
|
81
82
|
- docs/appending_events.md
|
82
83
|
- docs/configuration.md
|
@@ -226,6 +227,7 @@ files:
|
|
226
227
|
- lib/pg_eventstore/web/views/home/partials/events.erb
|
227
228
|
- lib/pg_eventstore/web/views/home/partials/pagination_links.erb
|
228
229
|
- lib/pg_eventstore/web/views/home/partials/stream_filter.erb
|
230
|
+
- lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb
|
229
231
|
- lib/pg_eventstore/web/views/layouts/application.erb
|
230
232
|
- lib/pg_eventstore/web/views/subscriptions/index.erb
|
231
233
|
- pg_eventstore.gemspec
|
@@ -331,6 +333,7 @@ files:
|
|
331
333
|
- sig/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rbs
|
332
334
|
- sig/pg_eventstore/utils.rbs
|
333
335
|
- sig/pg_eventstore/version.rbs
|
336
|
+
- sig/pg_eventstore/web/application.rbs
|
334
337
|
- sig/pg_eventstore/web/paginator/base_collection.rbs
|
335
338
|
- sig/pg_eventstore/web/paginator/event_types_collection.rbs
|
336
339
|
- sig/pg_eventstore/web/paginator/events_collection.rbs
|