glib-web 4.31.1 → 4.33.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/app/helpers/glib/json_ui/action_builder/http.rb +1 -0
- data/app/helpers/glib/json_ui/view_builder/charts.rb +2 -0
- data/app/helpers/glib/json_ui/view_builder/fields.rb +8 -0
- data/app/helpers/glib/json_ui/view_builder/panels.rb +2 -0
- data/app/views/json_ui/garage/actions/_detects.json.jbuilder +3 -1
- data/app/views/json_ui/garage/actions/_dialogs_show.json.jbuilder +23 -1
- data/app/views/json_ui/garage/actions/_http.json.jbuilder +18 -0
- data/app/views/json_ui/garage/panels/flow.json.jbuilder +17 -17
- data/app/views/json_ui/garage/views/charts.json.jbuilder +34 -0
- data/app/views/json_ui/garage/views/shareButton.json.jbuilder +7 -8
- data/lib/glib/json_crawler/action_crawlers/action_http.rb +1 -0
- data/lib/glib/json_crawler/action_crawlers/forms_submit.rb +2 -0
- data/lib/glib/json_crawler/action_crawlers/windows_open.rb +2 -1
- data/lib/glib/json_crawler/http.rb +4 -4
- data/lib/glib/json_crawler/router.rb +38 -6
- data/lib/glib/snapshot.rb +219 -0
- data/lib/glib/test_helpers.rb +11 -1
- data/lib/glib-web.rb +4 -1
- metadata +35 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3500da71e998b625c15ef1a41519581ed5d52d9dd9562a3e031e08d16aa2908c
|
4
|
+
data.tar.gz: 6f781cafa2ffe0cacfe1c7e31656415b2699bd19af4f9149614a5c229f60d94e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31478adf1a4abdd83df77febe25c6454ebdf66908ecef04b34cb4a48746878a8e0e058184c3cb093c7c4526831b948c539e79d88330c52a6a722fb5be885b4cc
|
7
|
+
data.tar.gz: 97f3164d075437ccb1bac37a6d0bea555ad1ec1b9a7b9870fb1d37a3619cc7a5a361a6684e0a412488d406da3c86dfa90416f7713445ead43bd9c355e18e9deb
|
@@ -10,7 +10,9 @@ section.rows builder: ->(template) do
|
|
10
10
|
end
|
11
11
|
template.thumbnail title: 'Detect Country', onClick: ->(action) do
|
12
12
|
action.browsers_detectCountry onDetect: ->(detect) do
|
13
|
-
detect.http_post url: json_ui_garage_url(path: 'forms/generic_post_all')
|
13
|
+
detect.http_post url: json_ui_garage_url(path: 'forms/generic_post_all'), formData: {
|
14
|
+
message: 'hello'
|
15
|
+
}
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -3,7 +3,29 @@ markdown = '## Emphasis' + "\n" +
|
|
3
3
|
"\n" +
|
4
4
|
'*This is italic text*' + "\n" +
|
5
5
|
"\n" +
|
6
|
-
'~~Strikethrough~~' + "\n"
|
6
|
+
'~~Strikethrough~~' + "\n" + "
|
7
|
+
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
8
|
+
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
|
9
|
+
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
|
10
|
+
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
|
11
|
+
and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
12
|
+
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
13
|
+
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
|
14
|
+
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
|
15
|
+
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
|
16
|
+
and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
17
|
+
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
18
|
+
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
|
19
|
+
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
|
20
|
+
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
|
21
|
+
and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
22
|
+
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
23
|
+
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
|
24
|
+
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
|
25
|
+
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
|
26
|
+
and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
27
|
+
"
|
28
|
+
|
7
29
|
|
8
30
|
options = {}
|
9
31
|
if (update_existing = local_assigns[:update_existing])
|
@@ -10,15 +10,33 @@ section.rows builder: ->(template) do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
template.thumbnail title: 'http/post (error page)', onClick: ->(action) do
|
14
|
+
action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
|
15
|
+
subaction.http_post retryLimit: 5, url: json_ui_garage_url(path: SecureRandom.hex(3)), formData: { user: { name: { first: 'New', last: 'Joe' } } }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
13
19
|
template.thumbnail title: 'http/patch', onClick: ->(action) do
|
14
20
|
action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
|
15
21
|
subaction.http_patch url: json_ui_garage_url(path: 'forms/basic_post'), formData: { 'user[name]' => 'Edit Joe' }
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
25
|
+
template.thumbnail title: 'http/patch (error page)', onClick: ->(action) do
|
26
|
+
action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
|
27
|
+
subaction.http_patch url: json_ui_garage_url(path: SecureRandom.hex(3)), formData: { 'user[name]' => 'Edit Joe' }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
19
31
|
template.thumbnail title: 'http/delete', onClick: ->(action) do
|
20
32
|
action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
|
21
33
|
subaction.http_delete url: json_ui_garage_url(path: 'forms/basic_post'), formData: { 'user[name]' => 'Delete Joe' }
|
22
34
|
end
|
23
35
|
end
|
36
|
+
|
37
|
+
template.thumbnail title: 'http/delete (error page)', onClick: ->(action) do
|
38
|
+
action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
|
39
|
+
subaction.http_delete url: json_ui_garage_url(path: SecureRandom.hex(3)), formData: { 'user[name]' => 'Delete Joe' }
|
40
|
+
end
|
41
|
+
end
|
24
42
|
end
|
@@ -60,21 +60,21 @@ page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
|
60
60
|
panel.button text: '5'
|
61
61
|
end
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
63
|
+
scroll.label text: "\n"
|
64
|
+
scroll.h1 text: 'Alignments'
|
65
|
+
scroll.panels_horizontal align: 'top', childViews: ->(panel) do
|
66
|
+
panel.button height: 50, text: 'Button'
|
67
|
+
panel.spacer width: 10
|
68
|
+
panel.label text: 'top'
|
69
|
+
end
|
70
|
+
scroll.panels_horizontal align: 'middle', childViews: ->(panel) do
|
71
|
+
panel.button height: 50, text: 'Button'
|
72
|
+
panel.spacer width: 10
|
73
|
+
panel.label text: 'middle'
|
74
|
+
end
|
75
|
+
scroll.panels_horizontal align: 'bottom', childViews: ->(panel) do
|
76
|
+
panel.button height: 50, text: 'Button'
|
77
|
+
panel.spacer width: 10
|
78
|
+
panel.label text: 'bottom'
|
79
|
+
end
|
80
80
|
end
|
@@ -138,6 +138,40 @@ else
|
|
138
138
|
plugins: default_plugins,
|
139
139
|
colors: ['#7A71F4', '#53B3E4', 'skyblue'], legend: { override: true }
|
140
140
|
|
141
|
+
|
142
|
+
scroll.h2 text: 'Column chart (Right-to-left)'
|
143
|
+
scroll.charts_column dataGroups: [
|
144
|
+
-> do
|
145
|
+
json.title 'Data 1'
|
146
|
+
|
147
|
+
points = {
|
148
|
+
'Hotel One' => 10,
|
149
|
+
'Hotel Two' => 16,
|
150
|
+
'Hotel Three' => 18,
|
151
|
+
}
|
152
|
+
json.points points
|
153
|
+
end,
|
154
|
+
-> do
|
155
|
+
json.title 'Data 2'
|
156
|
+
|
157
|
+
points = {
|
158
|
+
'Hotel One' => 24,
|
159
|
+
'Hotel Two' => 22,
|
160
|
+
}
|
161
|
+
json.points points
|
162
|
+
end,
|
163
|
+
-> do
|
164
|
+
json.title 'Data 3'
|
165
|
+
|
166
|
+
points = {
|
167
|
+
'Hotel One' => 20,
|
168
|
+
'Hotel Two' => 23,
|
169
|
+
}
|
170
|
+
json.points points
|
171
|
+
end
|
172
|
+
],
|
173
|
+
indexAxis: 'y', horizontalStacked: true,
|
174
|
+
colors: ['#7A71F4', '#53B3E4', 'red'], legend: { override: true }
|
141
175
|
scroll.h2 text: 'Column chart (Stacked)'
|
142
176
|
scroll.charts_column stacked: true, dataGroups: [
|
143
177
|
-> do
|
@@ -21,14 +21,13 @@ page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
|
21
21
|
|
22
22
|
f.spacer width: 10
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# end
|
24
|
+
f.shareButton \
|
25
|
+
network: 'email',
|
26
|
+
url: '',
|
27
|
+
text: 'Email',
|
28
|
+
onClick: ->(action) do
|
29
|
+
action.windows_openWeb url: 'mailto:test@email.com'
|
30
|
+
end
|
32
31
|
|
33
32
|
f.spacer width: 10
|
34
33
|
|
@@ -4,6 +4,7 @@ module Glib
|
|
4
4
|
def initialize(method, http, args, controller)
|
5
5
|
@http = http
|
6
6
|
json = @http.send(method, args['url'], controller, args.fetch('formData', {}))
|
7
|
+
@http.router.http_actions.add([args['action'], args['url'], args.fetch('formData', {})])
|
7
8
|
perform(json['onResponse'])
|
8
9
|
end
|
9
10
|
end
|
@@ -76,11 +76,13 @@ module Glib
|
|
76
76
|
case method
|
77
77
|
when :patch, :put
|
78
78
|
json = @http.patch url, action, params
|
79
|
+
@http.router.http_actions.add([action, url, params])
|
79
80
|
perform(json['onResponse'])
|
80
81
|
when :post
|
81
82
|
if (groups = form_post_param_groups)
|
82
83
|
groups.each do |group_params|
|
83
84
|
json = @http.post url, action, group_params
|
85
|
+
@http.router.http_actions.add([action, url, group_params])
|
84
86
|
perform(json['onResponse'])
|
85
87
|
end
|
86
88
|
else
|
@@ -24,19 +24,19 @@ module Glib
|
|
24
24
|
fetch(:get, url, action, params, inspect_result)
|
25
25
|
end
|
26
26
|
|
27
|
-
def post(url, action, params)
|
27
|
+
def post(url, action, params, inspect_result = true)
|
28
28
|
fetch(:post, url, action, params)
|
29
29
|
end
|
30
30
|
|
31
|
-
def patch(url, action, params)
|
31
|
+
def patch(url, action, params, inspect_result = true)
|
32
32
|
fetch(:patch, url, action, params)
|
33
33
|
end
|
34
34
|
|
35
|
-
def put(url, action, params)
|
35
|
+
def put(url, action, params, inspect_result = true)
|
36
36
|
fetch(:put, url, action, params)
|
37
37
|
end
|
38
38
|
|
39
|
-
def delete(url, action, params = {})
|
39
|
+
def delete(url, action, params = {}, inspect_result = true)
|
40
40
|
fetch(:delete, url, action, {})
|
41
41
|
end
|
42
42
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module Glib
|
2
2
|
module JsonCrawler
|
3
3
|
class Router
|
4
|
-
attr_reader :read_only_actions
|
4
|
+
attr_reader :read_only_actions # deprecated
|
5
|
+
attr_reader :logger, :last_log, :deferred_actions
|
6
|
+
attr_reader :http_actions
|
5
7
|
attr_accessor :host
|
6
8
|
|
7
9
|
def log(action, url, response = nil)
|
@@ -46,6 +48,7 @@ module Glib
|
|
46
48
|
@logger = ''
|
47
49
|
@visitor = Glib::Json::Traversal::Visitor.new(crawler_test: true)
|
48
50
|
@read_only_actions = Set.new
|
51
|
+
@http_actions = Set.new
|
49
52
|
# default rails's development host
|
50
53
|
@host ||= 'localhost:3000'
|
51
54
|
@page_specs = []
|
@@ -65,12 +68,14 @@ module Glib
|
|
65
68
|
end
|
66
69
|
|
67
70
|
if args.is_a?(Hash) && args['rel'] != 'nofollow'
|
68
|
-
|
71
|
+
on_click = args.fetch('onClick', nil)
|
72
|
+
|
73
|
+
if on_click && !args['disabled']
|
69
74
|
process_action(http, on_click)
|
70
75
|
end
|
71
76
|
end
|
72
77
|
|
73
|
-
@read_only_actions.replace(@read_only_actions.sort_by { |e| e[1].to_s })
|
78
|
+
# @read_only_actions.replace(@read_only_actions.sort_by { |e| e[1].to_s })
|
74
79
|
end
|
75
80
|
|
76
81
|
def process_action(http, spec)
|
@@ -83,14 +88,16 @@ module Glib
|
|
83
88
|
@depth += 1
|
84
89
|
case action
|
85
90
|
when 'initiate_navigation'
|
86
|
-
@read_only_actions.add([action, params['url']])
|
91
|
+
# @read_only_actions.add([action, params['url']])
|
92
|
+
http_actions.add([action, params['url']])
|
87
93
|
JsonCrawler::NavInitiate.new(http, params, action)
|
88
94
|
when 'runMultiple-v1', 'runMultiple'
|
89
95
|
JsonCrawler::RunMultiple.new(http, params, action)
|
90
96
|
when 'windows/open-v1', 'dialogs/open-v1', 'windows/reload-v1', 'windows/open',
|
91
97
|
'dialogs/open', 'windows/reload', 'windows/openWeb', 'windows/openWeb-v1'
|
92
98
|
if allowed?(params['url'])
|
93
|
-
@read_only_actions.add([action, params['url']])
|
99
|
+
# @read_only_actions.add([action, params['url']])
|
100
|
+
http_actions.add([action, params['url']])
|
94
101
|
JsonCrawler::WindowsOpen.new(http, params, action)
|
95
102
|
else
|
96
103
|
self.log action, params['url']
|
@@ -116,7 +123,8 @@ module Glib
|
|
116
123
|
'http/delete',
|
117
124
|
'dialogs/oauth'
|
118
125
|
].include?(action)
|
119
|
-
@read_only_actions.add([action, params['url']])
|
126
|
+
# @read_only_actions.add([action, params['url']])
|
127
|
+
http_actions.add([action, params['url']])
|
120
128
|
end
|
121
129
|
self.log action, params['url']
|
122
130
|
end
|
@@ -128,6 +136,7 @@ module Glib
|
|
128
136
|
@visitor.forms.last
|
129
137
|
end
|
130
138
|
|
139
|
+
# deprecated
|
131
140
|
def follow(http, target_routers)
|
132
141
|
if !target_routers.is_a?(Array)
|
133
142
|
target_routers = [target_routers]
|
@@ -145,6 +154,29 @@ module Glib
|
|
145
154
|
end
|
146
155
|
end
|
147
156
|
|
157
|
+
def follow_v2(http, crawler_actions)
|
158
|
+
@depth += 1
|
159
|
+
crawler_actions.each do |crawler_action|
|
160
|
+
action, url, params = crawler_action
|
161
|
+
|
162
|
+
params = JSON.parse(params) if params.is_a?(String)
|
163
|
+
params ||= {}
|
164
|
+
|
165
|
+
case action.to_s.downcase
|
166
|
+
when 'http/post-v1', 'forms/post'
|
167
|
+
http.post(url, action, params, false)
|
168
|
+
when 'http/patch-v1', 'forms/patch'
|
169
|
+
http.patch(url, action, params, false)
|
170
|
+
when 'http/put-v1', 'forms/put'
|
171
|
+
http.put(url, action, params, false)
|
172
|
+
when 'http/delete-v1'
|
173
|
+
http.delete(url, action, params, false)
|
174
|
+
else
|
175
|
+
http.get(url, action, params, false)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
148
180
|
def crawl_multiple(views, block)
|
149
181
|
@visitor.traverse_multiple views, block
|
150
182
|
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'active_snapshot'
|
2
|
+
require 'hashdiff'
|
3
|
+
|
4
|
+
module Glib
|
5
|
+
module Snapshot
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ::ActiveSnapshot
|
10
|
+
|
11
|
+
attr_accessor :snapshot_changed
|
12
|
+
attr_accessor :attributes_before_save
|
13
|
+
|
14
|
+
before_save do
|
15
|
+
self.attributes_before_save = attributes_in_database
|
16
|
+
self.snapshot_changed = changed?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def where_snapshots(user: nil, field: nil, action: nil, association: nil, association_action: nil, like: nil)
|
21
|
+
query = snapshots
|
22
|
+
|
23
|
+
if user.present? && !user.is_a?(String)
|
24
|
+
query = query.where(user_id: user.id, user_type: user.class.to_s)
|
25
|
+
elsif user.present? && user.is_a?(String)
|
26
|
+
query = query.where("metadata ->> 'user' = ?", user)
|
27
|
+
end
|
28
|
+
|
29
|
+
if field.present?
|
30
|
+
query = query.where("metadata -> 'diff' ->> 'item' LIKE ?", "%#{field}%")
|
31
|
+
end
|
32
|
+
|
33
|
+
if action.present?
|
34
|
+
query = query.where("metadata ->> 'action' = ?", action)
|
35
|
+
end
|
36
|
+
|
37
|
+
if association.present?
|
38
|
+
query = query.where("metadata -> 'diff' -> 'associations' -> '#{association}' -> 0 IS NOT NULL")
|
39
|
+
end
|
40
|
+
|
41
|
+
if association_action.present?
|
42
|
+
raise ArgumentError.new('association_action expect association to be present') if association.blank?
|
43
|
+
|
44
|
+
known_actions = {
|
45
|
+
'create' => '+',
|
46
|
+
'destroy' => '-',
|
47
|
+
'update' => '~'
|
48
|
+
}
|
49
|
+
query = query.where("metadata -> 'diff' -> 'associations' ->> '#{association}' LIKE ?", "%\"#{known_actions[association_action.to_s]}\"%")
|
50
|
+
end
|
51
|
+
|
52
|
+
if like.present?
|
53
|
+
query = query.where("metadata ->> 'diff' LIKE ?", "%#{like}%")
|
54
|
+
end
|
55
|
+
|
56
|
+
query.to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
# information need to be store:
|
60
|
+
# - action: create, update, destroy
|
61
|
+
# - track changes
|
62
|
+
def glib_create_snapshot!(user, action)
|
63
|
+
known_actions = ['create', 'update', 'destroy']
|
64
|
+
|
65
|
+
raise 'unknown action' if !known_actions.include?(action.to_s)
|
66
|
+
|
67
|
+
result = nil
|
68
|
+
with_lock do
|
69
|
+
version = last_version + 1
|
70
|
+
metadata = {
|
71
|
+
action: action,
|
72
|
+
diff: diff,
|
73
|
+
version: version
|
74
|
+
}
|
75
|
+
snapshot_obj = {
|
76
|
+
identifier: "#{self.class.to_s.underscore}_#{id}_version_#{version}",
|
77
|
+
user: user,
|
78
|
+
metadata: metadata
|
79
|
+
}
|
80
|
+
|
81
|
+
if user.is_a?(String)
|
82
|
+
metadata[:user] = user
|
83
|
+
snapshot_obj.delete(:user)
|
84
|
+
end
|
85
|
+
|
86
|
+
# dont create version if same as before
|
87
|
+
if !same_as_before?
|
88
|
+
result = create_snapshot!(**snapshot_obj)
|
89
|
+
remove_old_snapshot if result.present?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
def glib_revert_snapshot!(version)
|
96
|
+
snapshot = snapshots.find_by(identifier: "#{self.class.to_s.underscore}_#{id}_version_#{version}")
|
97
|
+
snapshot.restore!
|
98
|
+
end
|
99
|
+
|
100
|
+
def interval_from_prev_version
|
101
|
+
updated_at - snapshot_prev.created_at
|
102
|
+
end
|
103
|
+
|
104
|
+
def diff(snapshot = snapshot_prev)
|
105
|
+
default_ignored_keys = ['updated_at', 'created_at']
|
106
|
+
ignore_keys = default_ignored_keys
|
107
|
+
|
108
|
+
if !watched_keys_for_snapshot.nil?
|
109
|
+
ignored_keys_for_snapshot = attributes.except(*watched_keys_for_snapshot).keys
|
110
|
+
ignore_keys = (default_ignored_keys + ignored_keys_for_snapshot.map(&:to_s)).uniq
|
111
|
+
end
|
112
|
+
|
113
|
+
# always watch column with name *id
|
114
|
+
ignore_keys.filter! { |key| !key.ends_with?('id') || key != 'id' }
|
115
|
+
|
116
|
+
if snapshot.present?
|
117
|
+
item, associations = snapshot.fetch_reified_items
|
118
|
+
else
|
119
|
+
item = OpenStruct.new(attributes: attributes_before_save || {})
|
120
|
+
associations = {}
|
121
|
+
end
|
122
|
+
|
123
|
+
obj = {
|
124
|
+
'item' => ::Hashdiff.diff(item.attributes.except(*ignore_keys), attributes.except(*ignore_keys))
|
125
|
+
}
|
126
|
+
|
127
|
+
obj['associations'] = associations_for_snapshot.reduce({}) do |prev, curr|
|
128
|
+
first_record_off_same_collection = send(curr).first
|
129
|
+
|
130
|
+
if !first_record_off_same_collection.respond_to?(:watched_keys_for_snapshot) && !first_record_off_same_collection.nil?
|
131
|
+
raise NotImplementedError, "please add method 'watched_keys_for_snapshot' to #{first_record_off_same_collection.class}"
|
132
|
+
end
|
133
|
+
|
134
|
+
association_ignored_keys =
|
135
|
+
if !first_record_off_same_collection.try(:watched_keys_for_snapshot).nil?
|
136
|
+
assoc_ignore_keys = first_record_off_same_collection.attributes.except(*first_record_off_same_collection.watched_keys_for_snapshot).keys.map(&:to_s)
|
137
|
+
(default_ignored_keys + assoc_ignore_keys).uniq
|
138
|
+
else
|
139
|
+
default_ignored_keys
|
140
|
+
end
|
141
|
+
|
142
|
+
# always watch column with name *id
|
143
|
+
association_ignored_keys.filter! { |key| !key.ends_with?('id') || key != 'id' }
|
144
|
+
|
145
|
+
# attrs_before = (associations[curr] || []).map { |record| record.attributes.except(*association_ignored_keys) }
|
146
|
+
# attrs_now = send(curr).order(id: :asc).map { |record| record.attributes.except(*association_ignored_keys) }
|
147
|
+
|
148
|
+
before = (associations[curr] || [])
|
149
|
+
now = send(curr).order(id: :asc)
|
150
|
+
|
151
|
+
if before.blank?
|
152
|
+
prev.merge(curr.to_s => ::Hashdiff.diff([], now.map { |record| record.attributes.except(*association_ignored_keys) }))
|
153
|
+
else
|
154
|
+
diff = before.map.with_index do |record_before, index|
|
155
|
+
record_now = now.find_by(id: record_before.id)
|
156
|
+
if record_now.blank?
|
157
|
+
['-', "[#{index}]", record_before.attributes.except(*association_ignored_keys)]
|
158
|
+
elsif record_now.present?
|
159
|
+
next if Hashdiff.diff(record_before.attributes.except(*association_ignored_keys), record_now.attributes.except(*association_ignored_keys)).blank?
|
160
|
+
['~', "[#{index}]", record_before.attributes.except(*association_ignored_keys), record_now.attributes.except(*association_ignored_keys)]
|
161
|
+
end
|
162
|
+
end.compact_blank
|
163
|
+
|
164
|
+
diff2 = now.map.with_index do |record_now, index|
|
165
|
+
record_before = before.detect { |record| record.id == record_now.id }
|
166
|
+
if record_before.blank?
|
167
|
+
['+', "[#{index}]", record_now.attributes.except(*association_ignored_keys)]
|
168
|
+
end
|
169
|
+
end.compact_blank
|
170
|
+
|
171
|
+
prev.merge(curr.to_s => (diff + diff2))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
obj
|
176
|
+
end
|
177
|
+
|
178
|
+
def snapshot_prev
|
179
|
+
snapshots.order(id: :asc).last
|
180
|
+
end
|
181
|
+
|
182
|
+
def same_as_before?
|
183
|
+
return !snapshot_changed if snapshot_prev.blank?
|
184
|
+
|
185
|
+
result = diff['item'].blank?
|
186
|
+
|
187
|
+
result &&= diff['associations'].reduce(true) { |prev, (_k, v)| v.blank? && prev } if diff['associations'].present?
|
188
|
+
|
189
|
+
result
|
190
|
+
end
|
191
|
+
|
192
|
+
def last_version
|
193
|
+
return 0 if snapshot_prev.blank?
|
194
|
+
|
195
|
+
snapshot_prev.metadata['version']
|
196
|
+
end
|
197
|
+
|
198
|
+
def remove_old_snapshot
|
199
|
+
return if max_snapshots.nil?
|
200
|
+
|
201
|
+
newest_ids = snapshots.order(id: :desc).limit(max_snapshots).ids
|
202
|
+
return unless newest_ids.size >= max_snapshots
|
203
|
+
|
204
|
+
snapshots.where.not(id: newest_ids).destroy_all
|
205
|
+
end
|
206
|
+
|
207
|
+
def watched_keys_for_snapshot
|
208
|
+
raise NotImplementedError, "please add method 'watched_keys_for_snapshot' to #{self.class}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def associations_for_snapshot
|
212
|
+
children_to_snapshot.keys.filter { |key| respond_to?(key) }
|
213
|
+
end
|
214
|
+
|
215
|
+
def max_snapshots
|
216
|
+
10
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/lib/glib/test_helpers.rb
CHANGED
@@ -36,7 +36,7 @@ module Glib
|
|
36
36
|
assert_equal JSON.parse(expected), JSON.parse(result), "Result mismatch! #{__git_is_available? ? `git diff #{__controller_log_dir}/#{__controller_log_file}` : ''}"
|
37
37
|
end
|
38
38
|
|
39
|
-
def crawl_json_pages(user, check_result: true, log_file: nil, &block)
|
39
|
+
def crawl_json_pages(user, check_result: true, log_file: nil, dump_actions: false, &block)
|
40
40
|
__execute_crawler(user, check_result: true) do |router, http|
|
41
41
|
path = user[:path] ? "#{user[:path]}?format=json" : '/users/me?format=json&redirect=default'
|
42
42
|
router.host = HOST
|
@@ -47,6 +47,16 @@ module Glib
|
|
47
47
|
'url' => user[:url] || "http://#{HOST}#{path}"
|
48
48
|
}
|
49
49
|
)
|
50
|
+
|
51
|
+
if dump_actions
|
52
|
+
csv_string = router.http_actions.map do |row|
|
53
|
+
action, url, params = row
|
54
|
+
[action, url, JSON.generate(params)].to_csv
|
55
|
+
end.join
|
56
|
+
filename = "#{user[:email]}[#{user[:device]}][#{user[:version] || 'current'}].csv"
|
57
|
+
filepath = File.join(__crawler_log_dir, filename)
|
58
|
+
File.write(filepath, csv_string)
|
59
|
+
end
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
data/lib/glib-web.rb
CHANGED
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glib-web
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.33.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ''
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2019-10-04 00:00:00.000000000 Z
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.11'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: hashdiff
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: active_snapshot
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: rubocop
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,7 +122,7 @@ dependencies:
|
|
94
122
|
- - ">="
|
95
123
|
- !ruby/object:Gem::Version
|
96
124
|
version: '0'
|
97
|
-
description:
|
125
|
+
description:
|
98
126
|
email: ''
|
99
127
|
executables: []
|
100
128
|
extensions: []
|
@@ -389,16 +417,17 @@ files:
|
|
389
417
|
- lib/glib/json_crawler/http.rb
|
390
418
|
- lib/glib/json_crawler/router.rb
|
391
419
|
- lib/glib/mailer_tester.rb
|
420
|
+
- lib/glib/snapshot.rb
|
392
421
|
- lib/glib/test_helpers.rb
|
393
422
|
- lib/glib/time_freezable_mailer.rb
|
394
423
|
- lib/glib/time_returning_mailer.rb
|
395
424
|
- lib/glib/value.rb
|
396
425
|
- lib/glib/version.rb
|
397
426
|
- lib/tasks/db.rake
|
398
|
-
homepage:
|
427
|
+
homepage:
|
399
428
|
licenses: []
|
400
429
|
metadata: {}
|
401
|
-
post_install_message:
|
430
|
+
post_install_message:
|
402
431
|
rdoc_options: []
|
403
432
|
require_paths:
|
404
433
|
- lib
|
@@ -414,7 +443,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
414
443
|
version: '0'
|
415
444
|
requirements: []
|
416
445
|
rubygems_version: 3.4.6
|
417
|
-
signing_key:
|
446
|
+
signing_key:
|
418
447
|
specification_version: 4
|
419
448
|
summary: ''
|
420
449
|
test_files: []
|