pg_eventstore 1.0.0.rc1 → 1.0.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 +9 -0
- data/lib/pg_eventstore/queries/subscription_queries.rb +1 -1
- data/lib/pg_eventstore/queries/transaction_queries.rb +14 -4
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +2 -2
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +7 -0
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +48 -13
- data/lib/pg_eventstore/web/public/stylesheets/pg_eventstore.css +4 -0
- data/lib/pg_eventstore/web/views/layouts/application.erb +19 -0
- data/lib/pg_eventstore/web/views/subscriptions/index.erb +10 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebd667d51438040640b8edeee4d75fe2a88f6f0605c33f7dcb70a5a250d92efd
|
4
|
+
data.tar.gz: 5ca82322415c77be0ef1a44efe594bd6a3813552157e7bffcc67d64f8703718e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b339460c69fd29a43b23cc9d48847527e24a0c3a32b2524f03bec348058f1bad8873508cdf219d83a27ed4fefdb04ef3691aaeca9f67e93d78fff9556fbd1857
|
7
|
+
data.tar.gz: a122c0c6dcd1a65a12fbdf76df47167d26a727db6df47bd38122a0943622433dd410430450fcc9355bfc1ceb2b7f312eed03f1f25ababa4e897d56f8f3f60b76
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.0.0]
|
4
|
+
|
5
|
+
- Improve performance of Subscription#update by relaxing transaction isolation level
|
6
|
+
- Fix calculation of events number in the subscription's chunk
|
7
|
+
|
8
|
+
## [1.0.0.rc2]
|
9
|
+
|
10
|
+
- Implement confirmation dialog for sensitive admin UI actions
|
11
|
+
|
3
12
|
## [1.0.0.rc1]
|
4
13
|
|
5
14
|
- Improve performance of loading original events when resolve_link_tos: true option is provided
|
@@ -84,7 +84,7 @@ module PgEventstore
|
|
84
84
|
end.join(', ')
|
85
85
|
sql =
|
86
86
|
"UPDATE subscriptions SET #{attrs_sql} WHERE id = $#{attrs.keys.size + 1} RETURNING *"
|
87
|
-
updated_attrs = transaction_queries.transaction do
|
87
|
+
updated_attrs = transaction_queries.transaction(:read_committed) do
|
88
88
|
pg_result = connection.with do |conn|
|
89
89
|
conn.exec_params(sql, [*attrs.values, id])
|
90
90
|
end
|
@@ -3,6 +3,14 @@
|
|
3
3
|
module PgEventstore
|
4
4
|
# @!visibility private
|
5
5
|
class TransactionQueries
|
6
|
+
ISOLATION_LEVELS = {
|
7
|
+
read_committed: 'READ COMMITTED',
|
8
|
+
repeatable_read: 'REPEATABLE READ',
|
9
|
+
serializable: 'SERIALIZABLE'
|
10
|
+
}.tap do |h|
|
11
|
+
h.default = h[:serializable]
|
12
|
+
end.freeze
|
13
|
+
|
6
14
|
attr_reader :connection
|
7
15
|
private :connection
|
8
16
|
|
@@ -11,15 +19,16 @@ module PgEventstore
|
|
11
19
|
@connection = connection
|
12
20
|
end
|
13
21
|
|
22
|
+
# @param level [Symbol] transaction isolation level
|
14
23
|
# @return [void]
|
15
|
-
def transaction
|
24
|
+
def transaction(level = :serializable)
|
16
25
|
connection.with do |conn|
|
17
26
|
# We are inside a transaction already - no need to start another one
|
18
27
|
if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
|
19
28
|
next yield
|
20
29
|
end
|
21
30
|
|
22
|
-
pg_transaction(conn) do
|
31
|
+
pg_transaction(ISOLATION_LEVELS[level], conn) do
|
23
32
|
yield
|
24
33
|
end
|
25
34
|
end
|
@@ -27,11 +36,12 @@ module PgEventstore
|
|
27
36
|
|
28
37
|
private
|
29
38
|
|
39
|
+
# @param level [String] PostgreSQL transaction isolation level
|
30
40
|
# @param pg_connection [PG::Connection]
|
31
41
|
# @return [void]
|
32
|
-
def pg_transaction(pg_connection)
|
42
|
+
def pg_transaction(level, pg_connection)
|
33
43
|
pg_connection.transaction do
|
34
|
-
pg_connection.exec("SET TRANSACTION ISOLATION LEVEL
|
44
|
+
pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level}")
|
35
45
|
yield
|
36
46
|
end
|
37
47
|
rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
|
@@ -55,8 +55,8 @@ module PgEventstore
|
|
55
55
|
def estimate_events_number
|
56
56
|
return INITIAL_EVENTS_PER_CHUNK if @stats.average_event_processing_time.zero?
|
57
57
|
|
58
|
-
events_per_chunk = @subscription.chunk_query_interval / @stats.average_event_processing_time
|
59
|
-
[events_per_chunk, MAX_EVENTS_PER_CHUNK].min - @events_processor.events_left_in_chunk
|
58
|
+
events_per_chunk = (@subscription.chunk_query_interval / @stats.average_event_processing_time).round
|
59
|
+
[[events_per_chunk, MAX_EVENTS_PER_CHUNK].min - @events_processor.events_left_in_chunk, 0].max
|
60
60
|
end
|
61
61
|
|
62
62
|
# @return [void]
|
@@ -136,19 +136,6 @@ $(function(){
|
|
136
136
|
$(this).parents('tr').next().toggleClass('d-none');
|
137
137
|
});
|
138
138
|
|
139
|
-
// Handle a[data-method]. It works very similar to rails ujs
|
140
|
-
$('body').on('click', 'a[data-method]', function(e){
|
141
|
-
e.preventDefault();
|
142
|
-
|
143
|
-
let href = $(this).attr('href');
|
144
|
-
let method = $(this).data('method');
|
145
|
-
let $form = $(`<form method="${method}" action="${href}"></form>`);
|
146
|
-
let hashInput = `<input name="hash" value="${window.location.hash}" type="hidden" />`;
|
147
|
-
|
148
|
-
$form.hide().append(hashInput).appendTo('body');
|
149
|
-
$form.submit();
|
150
|
-
});
|
151
|
-
|
152
139
|
// When user navigates through SubscriptionsSet-s tabs - also change a hash of the url. Its value is send when
|
153
140
|
// clicking on a[data-method] links
|
154
141
|
$('.set-tab').click(function(e){
|
@@ -160,3 +147,51 @@ $(function(){
|
|
160
147
|
$(`.set-tab[href="${window.location.hash}"]`).get(0).click();
|
161
148
|
}
|
162
149
|
});
|
150
|
+
|
151
|
+
// Confirmation dialog and data-method handling functional
|
152
|
+
$(function(){
|
153
|
+
"use strict";
|
154
|
+
|
155
|
+
let $confirmationModal = $('#confirmation-modal');
|
156
|
+
|
157
|
+
$confirmationModal.on('hide.bs.modal', function(){
|
158
|
+
$(this).find('.modal-title').html('');
|
159
|
+
$(this).find('.modal-body').html('');
|
160
|
+
$(this).find('.confirm').off();
|
161
|
+
});
|
162
|
+
let showConfirmation = function(el, callback){
|
163
|
+
let $el = $(el);
|
164
|
+
$confirmationModal.find('.modal-body').html($el.data('confirm'));
|
165
|
+
$confirmationModal.find('.modal-title').html($el.data('confirm-title'));
|
166
|
+
$confirmationModal.modal('show');
|
167
|
+
$confirmationModal.one('click', '.confirm', callback);
|
168
|
+
}
|
169
|
+
|
170
|
+
let handleMethod = function(el){
|
171
|
+
let $el = $(el);
|
172
|
+
let href = $el.attr('href');
|
173
|
+
let method = $el.data('method') || 'GET';
|
174
|
+
|
175
|
+
if(method === 'GET') {
|
176
|
+
window.location.href = href;
|
177
|
+
}else{
|
178
|
+
let $form = $(`<form method="${method}" action="${href}"></form>`);
|
179
|
+
let hashInput = `<input name="hash" value="${window.location.hash}" type="hidden" />`;
|
180
|
+
|
181
|
+
$form.append(hashInput);
|
182
|
+
$form.hide().appendTo('body');
|
183
|
+
$form.submit();
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
$('body').on('click', 'a[data-confirm], a[data-method]', function(e){
|
188
|
+
e.preventDefault();
|
189
|
+
if($(this).data('confirm')) {
|
190
|
+
showConfirmation(e.target, function(){
|
191
|
+
handleMethod(e.target);
|
192
|
+
});
|
193
|
+
return;
|
194
|
+
}
|
195
|
+
handleMethod(e.target);
|
196
|
+
});
|
197
|
+
});
|
@@ -101,6 +101,25 @@
|
|
101
101
|
</div>
|
102
102
|
</div>
|
103
103
|
|
104
|
+
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog" aria-labelledby="confirmation-modal" aria-hidden="true">
|
105
|
+
<div class="modal-dialog modal-dialog-centered" role="document">
|
106
|
+
<div class="modal-content">
|
107
|
+
<div class="modal-header">
|
108
|
+
<h5 class="modal-title"></h5>
|
109
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
110
|
+
<span aria-hidden="true">×</span>
|
111
|
+
</button>
|
112
|
+
</div>
|
113
|
+
<div class="modal-body font-weight-bold text-break">
|
114
|
+
</div>
|
115
|
+
<div class="modal-footer">
|
116
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
117
|
+
<button type="button" class="btn btn-primary confirm">Continue</button>
|
118
|
+
</div>
|
119
|
+
</div>
|
120
|
+
</div>
|
121
|
+
</div>
|
122
|
+
|
104
123
|
<!-- jQuery -->
|
105
124
|
<script src="javascripts/vendor/jquery.min.js"></script>
|
106
125
|
<!-- Bootstrap -->
|
@@ -21,10 +21,6 @@
|
|
21
21
|
<div class="row">
|
22
22
|
<div class="col-md-12 col-sm-12">
|
23
23
|
<div class="x_panel">
|
24
|
-
<div class="x_title">
|
25
|
-
<h2><i class="fa fa-bars"></i> Tabs <small>Float left</small></h2>
|
26
|
-
<div class="clearfix"></div>
|
27
|
-
</div>
|
28
24
|
<div class="x_content">
|
29
25
|
<ul class="nav nav-tabs bar_tabs" role="tablist">
|
30
26
|
<% @association.association.each.with_index do |(subscriptions_set, _), index| %>
|
@@ -78,11 +74,11 @@
|
|
78
74
|
</a>
|
79
75
|
<% end %>
|
80
76
|
<% if PgEventstore::RunnerState::STATES.values_at(:running, :dead).include?(subscriptions_set.state) %>
|
81
|
-
<a class="btn btn-warning" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Stop]) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
|
77
|
+
<a class="btn btn-warning" data-confirm="You are about to stop SubscriptionsSet#<%= subscriptions_set.id %>. This will also delete it and will result in stopping all related Subscriptions. Continue?" data-confirm-title="Stop SubscriptionsSet" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Stop]) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
|
82
78
|
Stop
|
83
79
|
</a>
|
84
80
|
<% end %>
|
85
|
-
<a class="btn btn-danger" data-method="post" href="<%= url("/delete_subscriptions_set/#{subscriptions_set.id}") %>" data-toggle="tooltip" title="Use this action only on stuck Subscriptions Set - to clean it up.">
|
81
|
+
<a class="btn btn-danger" data-confirm="You are about to delete SubscriptionsSet#<%= subscriptions_set.id %>. Continue?" data-confirm-title="Delete SubscriptionsSet" data-method="post" href="<%= url("/delete_subscriptions_set/#{subscriptions_set.id}") %>" data-toggle="tooltip" title="Use this action only on stuck Subscriptions Set - to clean it up.">
|
86
82
|
Delete
|
87
83
|
</a>
|
88
84
|
</td>
|
@@ -108,11 +104,11 @@
|
|
108
104
|
<a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StartAll]) %>">
|
109
105
|
Start All
|
110
106
|
</a>
|
111
|
-
<a class="btn btn-danger" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StopAll]) %>">
|
107
|
+
<a class="btn btn-danger" data-confirm="You are about to stop all Subscriptions. Continue?" data-confirm-title="Stop all Subscriptions" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StopAll]) %>">
|
112
108
|
Stop All
|
113
109
|
</a>
|
114
110
|
<% else %>
|
115
|
-
<a class="btn btn-danger" data-method="post" href="<%= delete_all_subscriptions_url(subscriptions.map(&:id)) %>" data-toggle="tooltip" title="Delete all Subscriptions, displayed on the current page.">
|
111
|
+
<a class="btn btn-danger" data-confirm="You are about to delete all Subscriptions, listed on the page. Continue?" data-confirm-title="Delete all Subscriptions" data-method="post" href="<%= delete_all_subscriptions_url(subscriptions.map(&:id)) %>" data-toggle="tooltip" title="Delete all Subscriptions, displayed on the current page.">
|
116
112
|
Delete All
|
117
113
|
</a>
|
118
114
|
<% end %>
|
@@ -154,7 +150,11 @@
|
|
154
150
|
<td><%= subscription.chunk_query_interval %>s</td>
|
155
151
|
<td><%= subscription.last_chunk_fed_at %></td>
|
156
152
|
<td><%= colored_state(subscription.state, subscription.updated_at) %></td>
|
157
|
-
<td
|
153
|
+
<td>
|
154
|
+
<% if subscription.average_event_processing_time %>
|
155
|
+
<%= "#{(1 / subscription.average_event_processing_time).to_i}/s" %>
|
156
|
+
<% end %>
|
157
|
+
</td>
|
158
158
|
<td><%= subscription.restart_count %></td>
|
159
159
|
<td><%= subscription.max_restarts_number %></td>
|
160
160
|
<td><%= subscription.time_between_restarts %>s</td>
|
@@ -186,7 +186,7 @@
|
|
186
186
|
<% end %>
|
187
187
|
<% end %>
|
188
188
|
<% unless subscriptions_set.id %>
|
189
|
-
<a class="btn btn-danger" data-method="post" href="<%= url("/delete_subscription/#{subscription.id}") %>" data-toggle="tooltip" title="You will lose the Subscription's position as well.">
|
189
|
+
<a class="btn btn-danger" data-confirm="You are about to delete <%= h subscription.name.inspect %> Subscription. Continue?" data-confirm-title="Delete Subscription" data-method="post" href="<%= url("/delete_subscription/#{subscription.id}") %>" data-toggle="tooltip" title="You will lose the Subscription's position as well.">
|
190
190
|
Delete
|
191
191
|
</a>
|
192
192
|
<% 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.0.0
|
4
|
+
version: 1.0.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-
|
11
|
+
date: 2024-05-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|