pg_eventstore 0.10.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +26 -0
  4. data/db/migrations/1_create_events.sql +12 -11
  5. data/db/migrations/2_create_subscriptions.sql +6 -2
  6. data/db/migrations/3_create_subscription_commands.sql +9 -5
  7. data/db/migrations/4_create_subscriptions_set_commands.sql +1 -1
  8. data/db/migrations/5_partitions.sql +1 -0
  9. data/docs/configuration.md +1 -1
  10. data/docs/how_it_works.md +14 -1
  11. data/lib/pg_eventstore/commands/append.rb +1 -1
  12. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +30 -8
  13. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +8 -10
  14. data/lib/pg_eventstore/commands/link_to.rb +14 -7
  15. data/lib/pg_eventstore/errors.rb +10 -12
  16. data/lib/pg_eventstore/event.rb +9 -1
  17. data/lib/pg_eventstore/event_deserializer.rb +1 -0
  18. data/lib/pg_eventstore/queries/event_queries.rb +33 -6
  19. data/lib/pg_eventstore/queries/links_resolver.rb +53 -0
  20. data/lib/pg_eventstore/queries/partition_queries.rb +8 -0
  21. data/lib/pg_eventstore/queries/subscription_command_queries.rb +27 -7
  22. data/lib/pg_eventstore/queries/subscription_queries.rb +70 -35
  23. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +13 -1
  24. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +18 -4
  25. data/lib/pg_eventstore/queries.rb +1 -0
  26. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +4 -17
  27. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +10 -2
  28. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +9 -7
  29. data/lib/pg_eventstore/subscriptions/commands_handler.rb +3 -2
  30. data/lib/pg_eventstore/subscriptions/events_processor.rb +10 -2
  31. data/lib/pg_eventstore/subscriptions/subscription.rb +29 -12
  32. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +20 -16
  33. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +1 -1
  34. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +3 -4
  35. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +22 -1
  36. data/lib/pg_eventstore/version.rb +1 -1
  37. data/lib/pg_eventstore/web/application.rb +180 -0
  38. data/lib/pg_eventstore/web/paginator/base_collection.rb +56 -0
  39. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +50 -0
  40. data/lib/pg_eventstore/web/paginator/events_collection.rb +105 -0
  41. data/lib/pg_eventstore/web/paginator/helpers.rb +119 -0
  42. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +48 -0
  43. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +50 -0
  44. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +51 -0
  45. data/lib/pg_eventstore/web/public/fonts/vendor/FontAwesome.otf +0 -0
  46. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.eot +0 -0
  47. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.svg +685 -0
  48. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.ttf +0 -0
  49. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff +0 -0
  50. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff2 +0 -0
  51. data/lib/pg_eventstore/web/public/images/favicon.ico +0 -0
  52. data/lib/pg_eventstore/web/public/javascripts/gentelella.js +334 -0
  53. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +162 -0
  54. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js +7 -0
  55. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js.map +1 -0
  56. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.autocomplete.min.js +8 -0
  57. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js +4 -0
  58. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js.map +1 -0
  59. data/lib/pg_eventstore/web/public/javascripts/vendor/select2.full.min.js +2 -0
  60. data/lib/pg_eventstore/web/public/stylesheets/pg_eventstore.css +5 -0
  61. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css +7 -0
  62. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css.map +1 -0
  63. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css +4 -0
  64. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css.map +7 -0
  65. data/lib/pg_eventstore/web/public/stylesheets/vendor/gentelella.min.css +13 -0
  66. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2-bootstrap4.min.css +3 -0
  67. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2.min.css +2 -0
  68. data/lib/pg_eventstore/web/subscriptions/helpers.rb +76 -0
  69. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +34 -0
  70. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +33 -0
  71. data/lib/pg_eventstore/web/subscriptions/subscriptions_set.rb +33 -0
  72. data/lib/pg_eventstore/web/subscriptions/subscriptions_to_set_association.rb +32 -0
  73. data/lib/pg_eventstore/web/views/home/dashboard.erb +147 -0
  74. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +15 -0
  75. data/lib/pg_eventstore/web/views/home/partials/events.erb +22 -0
  76. data/lib/pg_eventstore/web/views/home/partials/pagination_links.erb +3 -0
  77. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +31 -0
  78. data/lib/pg_eventstore/web/views/layouts/application.erb +116 -0
  79. data/lib/pg_eventstore/web/views/subscriptions/index.erb +220 -0
  80. data/lib/pg_eventstore/web.rb +22 -0
  81. data/lib/pg_eventstore.rb +5 -0
  82. data/pg_eventstore.gemspec +2 -1
  83. metadata +61 -2
File without changes
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Resize function without multiple trigger
3
+ *
4
+ * Usage:
5
+ * $(window).smartresize(function(){
6
+ * // code here
7
+ * });
8
+ */
9
+ (function($,sr){
10
+ // debouncing function from John Hann
11
+ // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
12
+ var debounce = function (func, threshold, execAsap) {
13
+ var timeout;
14
+
15
+ return function debounced () {
16
+ var obj = this, args = arguments;
17
+ function delayed () {
18
+ if (!execAsap)
19
+ func.apply(obj, args);
20
+ timeout = null;
21
+ }
22
+
23
+ if (timeout)
24
+ clearTimeout(timeout);
25
+ else if (execAsap)
26
+ func.apply(obj, args);
27
+
28
+ timeout = setTimeout(delayed, threshold || 100);
29
+ };
30
+ };
31
+
32
+ // smartresize
33
+ jQuery.fn[sr] = function(fn){ return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
34
+
35
+ })(jQuery,'smartresize');
36
+ /**
37
+ * To change this license header, choose License Headers in Project Properties.
38
+ * To change this template file, choose Tools | Templates
39
+ * and open the template in the editor.
40
+ */
41
+
42
+ var CURRENT_URL = window.location.href.split('#')[0].split('?')[0],
43
+ $BODY = $('body'),
44
+ $MENU_TOGGLE = $('#menu_toggle'),
45
+ $SIDEBAR_MENU = $('#sidebar-menu'),
46
+ $SIDEBAR_FOOTER = $('.sidebar-footer'),
47
+ $LEFT_COL = $('.left_col'),
48
+ $RIGHT_COL = $('.right_col'),
49
+ $NAV_MENU = $('.nav_menu'),
50
+ $FOOTER = $('footer');
51
+
52
+ // Sidebar
53
+ function init_sidebar() {
54
+ // TODO: This is some kind of easy fix, maybe we can improve this
55
+ var setContentHeight = function () {
56
+ // reset height
57
+ $RIGHT_COL.css('min-height', $(window).height());
58
+
59
+ var bodyHeight = $BODY.outerHeight(),
60
+ footerHeight = $BODY.hasClass('footer_fixed') ? -10 : $FOOTER.height(),
61
+ leftColHeight = $LEFT_COL.eq(1).height() + $SIDEBAR_FOOTER.height(),
62
+ contentHeight = bodyHeight < leftColHeight ? leftColHeight : bodyHeight;
63
+
64
+ // normalize content
65
+ contentHeight -= $NAV_MENU.height() + footerHeight;
66
+
67
+ $RIGHT_COL.css('min-height', contentHeight);
68
+ };
69
+
70
+ var openUpMenu = function () {
71
+ $SIDEBAR_MENU.find('li').removeClass('active active-sm');
72
+ $SIDEBAR_MENU.find('li ul').slideUp();
73
+ }
74
+
75
+ $SIDEBAR_MENU.find('a').on('click', function (ev) {
76
+ var $li = $(this).parent();
77
+
78
+ if ($li.is('.active')) {
79
+ $li.removeClass('active active-sm');
80
+ $('ul:first', $li).slideUp(function () {
81
+ setContentHeight();
82
+ });
83
+ } else {
84
+ // prevent closing menu if we are on child menu
85
+ if (!$li.parent().is('.child_menu')) {
86
+ openUpMenu();
87
+ } else {
88
+ if ($BODY.is('nav-sm')) {
89
+ if (!$li.parent().is('child_menu')) {
90
+ openUpMenu();
91
+ }
92
+ }
93
+ }
94
+
95
+ $li.addClass('active');
96
+
97
+ $('ul:first', $li).slideDown(function () {
98
+ setContentHeight();
99
+ });
100
+ }
101
+ });
102
+
103
+ // toggle small or large menu
104
+ $MENU_TOGGLE.on('click', function () {
105
+ if ($BODY.hasClass('nav-md')) {
106
+ $SIDEBAR_MENU.find('li.active ul').hide();
107
+ $SIDEBAR_MENU.find('li.active').addClass('active-sm').removeClass('active');
108
+ } else {
109
+ $SIDEBAR_MENU.find('li.active-sm ul').show();
110
+ $SIDEBAR_MENU.find('li.active-sm').addClass('active').removeClass('active-sm');
111
+ }
112
+
113
+ $BODY.toggleClass('nav-md nav-sm');
114
+
115
+ setContentHeight();
116
+
117
+ $('.dataTable').each(function () { $(this).dataTable().fnDraw(); });
118
+ });
119
+
120
+ // check active menu
121
+ $SIDEBAR_MENU.find('a[href="' + CURRENT_URL + '"]').parent('li').addClass('current-page');
122
+
123
+ $SIDEBAR_MENU.find('a').filter(function () {
124
+ return this.href == CURRENT_URL;
125
+ }).parent('li').addClass('current-page').parents('ul').slideDown(function () {
126
+ setContentHeight();
127
+ }).parent().addClass('active');
128
+
129
+ // recompute content when resizing
130
+ $(window).smartresize(function () {
131
+ setContentHeight();
132
+ });
133
+
134
+ setContentHeight();
135
+
136
+ // fixed sidebar
137
+ if ($.fn.mCustomScrollbar) {
138
+ $('.menu_fixed').mCustomScrollbar({
139
+ autoHideScrollbar: true,
140
+ theme: 'minimal',
141
+ mouseWheel: { preventDefault: true }
142
+ });
143
+ }
144
+ }
145
+ // /Sidebar
146
+
147
+ // Panel toolbox
148
+ $(document).ready(function () {
149
+ $('.collapse-link').on('click', function () {
150
+ var $BOX_PANEL = $(this).closest('.x_panel'),
151
+ $ICON = $(this).find('i'),
152
+ $BOX_CONTENT = $BOX_PANEL.find('.x_content');
153
+
154
+ // fix for some div with hardcoded fix class
155
+ if ($BOX_PANEL.attr('style')) {
156
+ $BOX_CONTENT.slideToggle(200, function () {
157
+ $BOX_PANEL.removeAttr('style');
158
+ });
159
+ } else {
160
+ $BOX_CONTENT.slideToggle(200);
161
+ $BOX_PANEL.css('height', 'auto');
162
+ }
163
+
164
+ $ICON.toggleClass('fa-chevron-up fa-chevron-down');
165
+ });
166
+
167
+ $('.close-link').click(function () {
168
+ var $BOX_PANEL = $(this).closest('.x_panel');
169
+
170
+ $BOX_PANEL.remove();
171
+ });
172
+ });
173
+ // /Panel toolbox
174
+
175
+ // Tooltip
176
+ $(document).ready(function () {
177
+ $('[data-toggle="tooltip"]').tooltip({
178
+ container: 'body'
179
+ });
180
+ });
181
+ // /Tooltip
182
+
183
+ // Progressbar
184
+ $(document).ready(function () {
185
+ if ($(".progress .progress-bar")[0]) {
186
+ $('.progress .progress-bar').progressbar();
187
+ }
188
+ });
189
+ // /Progressbar
190
+
191
+ // Switchery
192
+ $(document).ready(function () {
193
+ if ($(".js-switch")[0]) {
194
+ var elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
195
+ elems.forEach(function (html) {
196
+ var switchery = new Switchery(html, {
197
+ color: '#26B99A'
198
+ });
199
+ });
200
+ }
201
+ });
202
+ // /Switchery
203
+
204
+ // iCheck
205
+ $(document).ready(function () {
206
+ if ($("input.flat")[0]) {
207
+ $(document).ready(function () {
208
+ $('input.flat').iCheck({
209
+ checkboxClass: 'icheckbox_flat-green',
210
+ radioClass: 'iradio_flat-green'
211
+ });
212
+ });
213
+ }
214
+ });
215
+ // /iCheck
216
+
217
+ // Table
218
+ $('table input').on('ifChecked', function () {
219
+ checkState = '';
220
+ $(this).parent().parent().parent().addClass('selected');
221
+ countChecked();
222
+ });
223
+ $('table input').on('ifUnchecked', function () {
224
+ checkState = '';
225
+ $(this).parent().parent().parent().removeClass('selected');
226
+ countChecked();
227
+ });
228
+
229
+ var checkState = '';
230
+
231
+ $('.bulk_action input').on('ifChecked', function () {
232
+ checkState = '';
233
+ $(this).parent().parent().parent().addClass('selected');
234
+ countChecked();
235
+ });
236
+ $('.bulk_action input').on('ifUnchecked', function () {
237
+ checkState = '';
238
+ $(this).parent().parent().parent().removeClass('selected');
239
+ countChecked();
240
+ });
241
+ $('.bulk_action input#check-all').on('ifChecked', function () {
242
+ checkState = 'all';
243
+ countChecked();
244
+ });
245
+ $('.bulk_action input#check-all').on('ifUnchecked', function () {
246
+ checkState = 'none';
247
+ countChecked();
248
+ });
249
+
250
+ function countChecked() {
251
+ if (checkState === 'all') {
252
+ $(".bulk_action input[name='table_records']").iCheck('check');
253
+ }
254
+ if (checkState === 'none') {
255
+ $(".bulk_action input[name='table_records']").iCheck('uncheck');
256
+ }
257
+
258
+ var checkCount = $(".bulk_action input[name='table_records']:checked").length;
259
+
260
+ if (checkCount) {
261
+ $('.column-title').hide();
262
+ $('.bulk-actions').show();
263
+ $('.action-cnt').html(checkCount + ' Records Selected');
264
+ } else {
265
+ $('.column-title').show();
266
+ $('.bulk-actions').hide();
267
+ }
268
+ }
269
+
270
+ // Accordion
271
+ $(document).ready(function () {
272
+ $(".expand").on("click", function () {
273
+ $(this).next().slideToggle(200);
274
+ let $expand = $(this).find(">:first-child");
275
+
276
+ if ($expand.text() == "+") {
277
+ $expand.text("-");
278
+ } else {
279
+ $expand.text("+");
280
+ }
281
+ });
282
+ });
283
+
284
+
285
+ //hover and retain popover when on popover content
286
+ var originalLeave = $.fn.popover.Constructor.prototype.leave;
287
+ $.fn.popover.Constructor.prototype.leave = function (obj) {
288
+ var self = obj instanceof this.constructor ?
289
+ obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
290
+ var container, timeout;
291
+
292
+ originalLeave.call(this, obj);
293
+
294
+ if (obj.currentTarget) {
295
+ container = $(obj.currentTarget).siblings('.popover');
296
+ timeout = self.timeout;
297
+ container.one('mouseenter', function () {
298
+ //We entered the actual popover – call off the dogs
299
+ clearTimeout(timeout);
300
+ //Let's monitor popover content instead
301
+ container.one('mouseleave', function () {
302
+ $.fn.popover.Constructor.prototype.leave.call(self, self);
303
+ });
304
+ });
305
+ }
306
+ };
307
+
308
+ $('body').popover({
309
+ selector: '[data-popover]',
310
+ trigger: 'click hover',
311
+ delay: {
312
+ show: 50,
313
+ hide: 400
314
+ }
315
+ });
316
+
317
+
318
+ function gd(year, month, day) {
319
+ return new Date(year, month - 1, day).getTime();
320
+ }
321
+
322
+ /* AUTOSIZE */
323
+
324
+ function init_autosize() {
325
+ if (typeof $.fn.autosize !== 'undefined') {
326
+ autosize($('.resizable_textarea'));
327
+ }
328
+ };
329
+
330
+
331
+ $(document).ready(function () {
332
+ init_sidebar();
333
+ init_autosize();
334
+ });
@@ -0,0 +1,162 @@
1
+ $(function(){
2
+ "use strict";
3
+
4
+ let initStreamFilterAutocomplete = function($filter) {
5
+ let $contextSelect = $filter.find('select[name*="context"]');
6
+ let $streamNameSelect = $filter.find('select[name*="stream_name"]');
7
+ let $streamIdSelect = $filter.find('select[name*="stream_id"]');
8
+ $contextSelect.select2({
9
+ ajax: {
10
+ url: $contextSelect.data('url'),
11
+ processResults: function(data, params){
12
+ params.starting_id = data.pagination.starting_id;
13
+ data.results.forEach(function(contextAttrs){
14
+ contextAttrs.id = contextAttrs.context;
15
+ contextAttrs.text = contextAttrs.context;
16
+ });
17
+ return data;
18
+ },
19
+ },
20
+ allowClear: true
21
+ });
22
+ $streamNameSelect.select2({
23
+ ajax: {
24
+ url: $streamNameSelect.data('url'),
25
+ data: function (params) {
26
+ params.context = $contextSelect.val();
27
+
28
+ return params;
29
+ },
30
+ processResults: function(data, params){
31
+ params.starting_id = data.pagination.starting_id;
32
+ data.results.forEach(function(streamNameAttrs){
33
+ streamNameAttrs.id = streamNameAttrs.stream_name;
34
+ streamNameAttrs.text = streamNameAttrs.stream_name;
35
+ });
36
+ return data;
37
+ },
38
+ },
39
+ allowClear: true
40
+ });
41
+ $streamIdSelect.select2({
42
+ ajax: {
43
+ url: $streamIdSelect.data('url'),
44
+ data: function (params) {
45
+ params.context = $contextSelect.val();
46
+ params.stream_name = $streamNameSelect.val();
47
+
48
+ return params;
49
+ },
50
+ processResults: function(data, params){
51
+ params.starting_id = data.pagination.starting_id;
52
+ data.results.forEach(function(streamNameAttrs){
53
+ streamNameAttrs.id = streamNameAttrs.stream_id;
54
+ streamNameAttrs.text = streamNameAttrs.stream_id;
55
+ });
56
+ return data;
57
+ },
58
+ },
59
+ allowClear: true
60
+ });
61
+ }
62
+
63
+ let initEventTypeFilterAutocomplete = function($filter) {
64
+ let $eventTypeSelect = $filter.find('select');
65
+ $eventTypeSelect.select2({
66
+ ajax: {
67
+ url: $eventTypeSelect.data('url'),
68
+ processResults: function(data, params){
69
+ params.starting_id = data.pagination.starting_id;
70
+ data.results.forEach(function(contextAttrs){
71
+ contextAttrs.id = contextAttrs.event_type;
72
+ contextAttrs.text = contextAttrs.event_type;
73
+ });
74
+ return data;
75
+ },
76
+ },
77
+ allowClear: true
78
+ });
79
+ }
80
+
81
+ // Per page drop down
82
+ $('#per_page_select').change(function(e){
83
+ window.location.href = $(this).find('option:selected').data('url');
84
+ });
85
+
86
+ let $filtersForm = $('#filters-form');
87
+ // Stream filter template
88
+ let streamFilterTmpl = $('#stream-filter-tmpl').text();
89
+ // Event type filter template
90
+ let eventFilterTmpl = $('#event-type-filter-tmpl').text();
91
+
92
+ // Remove filter button
93
+ $filtersForm.on('click', '.remove-filter', function(){
94
+ $(this).parents('.form-row').remove();
95
+ });
96
+ // Add stream filter button
97
+ $filtersForm.on('click', '.add-stream-filter', function(){
98
+ let filtersNum = $filtersForm.find('.stream-filters').children().length + '';
99
+ $filtersForm.find('.stream-filters').append(streamFilterTmpl.replace(/%NUM%/g, filtersNum));
100
+ initStreamFilterAutocomplete($filtersForm.find('.stream-filters').children().last());
101
+ });
102
+ // Add event type filter button
103
+ $filtersForm.on('click', '.add-event-filter', function(){
104
+ $filtersForm.find('.event-filters').append(eventFilterTmpl);
105
+ initEventTypeFilterAutocomplete($filtersForm.find('.event-filters').children().last());
106
+ });
107
+ // Init select2 for stream filters which were initially rendered
108
+ $filtersForm.find('.stream-filters').children().each(function(){
109
+ initStreamFilterAutocomplete($(this));
110
+ });
111
+ // Init select2 for event type filters which were initially rendered
112
+ $filtersForm.find('.event-filters').children().each(function(){
113
+ initEventTypeFilterAutocomplete($(this));
114
+ });
115
+
116
+ let autoRefreshInterval;
117
+ $('#auto-refresh').change(function(){
118
+ if($(this).is(':checked')) {
119
+ autoRefreshInterval = setInterval(function(){
120
+ $.getJSON(window.location.href).success(function(response, textStatus, xhr){
121
+ console.log(textStatus);
122
+ if(textStatus === 'success') {
123
+ $('#events-table').find('tbody').html(response.events);
124
+ $('#total-count').html(response.total_count);
125
+ $('#pagination').html(response.pagination);
126
+ }
127
+ });
128
+ }, 2000);
129
+ } else {
130
+ clearInterval(autoRefreshInterval);
131
+ }
132
+ });
133
+
134
+ // Toggle event's JSON data/metadata details
135
+ $('#events-table').on('click', '.toggle-event-data', function(){
136
+ $(this).parents('tr').next().toggleClass('d-none');
137
+ });
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
+ // When user navigates through SubscriptionsSet-s tabs - also change a hash of the url. Its value is send when
153
+ // clicking on a[data-method] links
154
+ $('.set-tab').click(function(e){
155
+ window.location.hash = $(this).attr('href');
156
+ })
157
+ // Open correct SubscriptionsSet tab on page load based on location's hash value. So, e.g., if you navigated to
158
+ // #set-1 SubscriptionsSet, then, on page reload - the correct tab will be opened
159
+ if(window.location.hash !== "") {
160
+ $(`.set-tab[href="${window.location.hash}"]`).get(0).click();
161
+ }
162
+ });