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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbcfe058d0769d031f29580fdd79e899a73987a3108b0b96ad154a5bb913a7d2
4
- data.tar.gz: 0f071621693bca4b597defc2be13fbde054be06448648ace7324e8ce14e4e512
3
+ metadata.gz: ebd667d51438040640b8edeee4d75fe2a88f6f0605c33f7dcb70a5a250d92efd
4
+ data.tar.gz: 5ca82322415c77be0ef1a44efe594bd6a3813552157e7bffcc67d64f8703718e
5
5
  SHA512:
6
- metadata.gz: fa8828e002dab641f15b8ed3756c822899b3937929482a8fb9dcbac4bb5c10b7283427fa462a627ee47209d4476157396749ced15d50b64892683b1309e897a8
7
- data.tar.gz: 3482263831bc256851999a0093be615cf4e238ceeae5c27f209f2fcc3d8b666db8b4c68bc388e5ef0c52de8f52a65e6abfe4db864042161f823b867f4996e750
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 SERIALIZABLE")
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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "1.0.0.rc1"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -53,6 +53,13 @@ module PgEventstore
53
53
 
54
54
  "#{request.referer}#{params[:hash]}"
55
55
  end
56
+
57
+ # Shortcut to escape html
58
+ # @param text [String]
59
+ # @return [String]
60
+ def h(text)
61
+ Rack::Utils.escape_html(text)
62
+ end
56
63
  end
57
64
 
58
65
  get '/' do
@@ -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
+ });
@@ -3,3 +3,7 @@ tr.collapsing {
3
3
  transition: none;
4
4
  display: none;
5
5
  }
6
+
7
+ #confirmation-modal .modal-body {
8
+ font-size: 1.2rem;
9
+ }
@@ -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">&times;</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><%= subscription.average_event_processing_time %></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.rc1
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-04-05 00:00:00.000000000 Z
11
+ date: 2024-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg