Skip to content
This repository was archived by the owner on Jan 30, 2024. It is now read-only.

Commit 2c6a4d3

Browse files
lucasscariotarnaudbesnier
authored andcommitted
[-] Apimap - Prevent random sorting collections and useless updates. (ForestAdmin#231)
1 parent a89d452 commit 2c6a4d3

File tree

10 files changed

+414
-2
lines changed

10 files changed

+414
-2
lines changed

.rspec

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--require spec_helper

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ rvm:
66
- 2.2.0
77
script:
88
- RAILS_ENV=test bundle exec rake --trace db:migrate test
9+
- RAILS_ENV=test bundle exec rspec

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44
### Changed
5+
- Apimap - Prevent random sorting collections and useless updates.
56
- Smart Fields - Compute only the necessary Smart Fields values for list views and CSV exports.
67

78
## RELEASE 2.2.2 - 2018-01-30

Gemfile

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ gemspec
1111
# your gem to rubygems.org.
1212

1313
# To use a debugger
14-
gem 'byebug', group: [:development, :test]
14+
group :development, :test do
15+
gem 'byebug'
16+
gem 'rspec'
17+
end
1518

1619
group :test do
1720
gem 'rake'

Gemfile.lock

+15
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ GEM
5757
byebug (8.2.2)
5858
concurrent-ruby (1.0.5)
5959
crass (1.0.3)
60+
diff-lcs (1.3)
6061
erubis (2.7.0)
6162
globalid (0.4.1)
6263
activesupport (>= 4.2.0)
@@ -107,6 +108,19 @@ GEM
107108
rake (>= 0.8.7)
108109
thor (>= 0.18.1, < 2.0)
109110
rake (12.3.0)
111+
rspec (3.7.0)
112+
rspec-core (~> 3.7.0)
113+
rspec-expectations (~> 3.7.0)
114+
rspec-mocks (~> 3.7.0)
115+
rspec-core (3.7.1)
116+
rspec-support (~> 3.7.0)
117+
rspec-expectations (3.7.0)
118+
diff-lcs (>= 1.2.0, < 2.0)
119+
rspec-support (~> 3.7.0)
120+
rspec-mocks (3.7.0)
121+
diff-lcs (>= 1.2.0, < 2.0)
122+
rspec-support (~> 3.7.0)
123+
rspec-support (3.7.1)
110124
sprockets (3.7.1)
111125
concurrent-ruby (~> 1.0)
112126
rack (> 1, < 3)
@@ -135,6 +149,7 @@ DEPENDENCIES
135149
rack-cors
136150
rails (= 4.2.7.1)
137151
rake
152+
rspec
138153
sqlite3
139154
useragent
140155

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
module ForestLiana
2+
class ApimapSorter
3+
def initialize apimap
4+
@apimap = apimap.stringify_keys
5+
end
6+
7+
def perform
8+
begin
9+
@apimap = reorder_keys_basic(@apimap)
10+
@apimap['data'] = sort_array_of_objects(@apimap['data']);
11+
12+
@apimap['data'].map! do |collection|
13+
collection = reorder_keys_child(collection)
14+
collection['attributes'] = reorder_keys_collection(collection['attributes'])
15+
if collection['attributes']['fields']
16+
collection['attributes']['fields'] = sort_array_of_fields(collection['attributes']['fields'])
17+
collection['attributes']['fields'].map! { |field| reorder_keys_field(field) }
18+
end
19+
collection
20+
end
21+
22+
if @apimap['included']
23+
@apimap['included'] = sort_array_of_objects(@apimap['included'])
24+
25+
@apimap['included'].map! do |object|
26+
object = reorder_keys_child(object)
27+
object['attributes'] = reorder_keys_collection(object['attributes'])
28+
if object['attributes']['fields']
29+
object['attributes']['fields'] = sort_array_of_fields(object['attributes']['fields'])
30+
object['attributes']['fields'].map! { |field| reorder_keys_field(field) }
31+
end
32+
object
33+
end
34+
end
35+
36+
@apimap['meta'] = reorder_keys_basic(@apimap['meta'])
37+
@apimap
38+
rescue => exception
39+
FOREST_LOGGER.warn "An Apimap reordering issue occured: #{exception}"
40+
@apimap
41+
end
42+
end
43+
44+
private
45+
46+
def sort_array_of_objects(array)
47+
array.sort do |element1, element2|
48+
if element1['type'] == element2['type']
49+
element1['id'] <=> element2['id']
50+
else
51+
element1['type'] <=> element2['type']
52+
end
53+
end
54+
end
55+
56+
def sort_array_of_fields(array)
57+
return nil unless array
58+
59+
array.sort do |field1, field2|
60+
if field1['field'] == field2['field']
61+
field1['type'] <=> field2['type']
62+
else
63+
field1['field'] <=> field2['field']
64+
end
65+
end
66+
end
67+
68+
def reorder_keys_basic(object)
69+
object = object.stringify_keys
70+
object_reordered = {}
71+
object.keys.sort.each do |key|
72+
object_reordered[key] = object[key]
73+
end
74+
object_reordered
75+
end
76+
77+
def reorder_keys_child(object)
78+
object = object.stringify_keys
79+
object_reordered = {}
80+
object_reordered['type'] = object['type']
81+
object_reordered['id'] = object['id']
82+
object_reordered['attributes'] = object['attributes']
83+
object.keys.sort.each { |key| object_reordered[key] = object[key] }
84+
object_reordered
85+
end
86+
87+
def reorder_keys_collection(collection)
88+
collection = collection.stringify_keys
89+
collection_reordered_start = {}
90+
collection_reordered_start['name'] = collection['name']
91+
collection_reordered_end = {}
92+
collection_reordered_end['fields'] = collection['fields'] if collection['fields']
93+
94+
collection.delete('name')
95+
collection.delete('fields')
96+
97+
collection_reordered_middle = reorder_keys_basic(collection)
98+
99+
collection = collection_reordered_start.merge(collection_reordered_middle)
100+
collection.merge(collection_reordered_end)
101+
end
102+
103+
def reorder_keys_field(field)
104+
field = field.stringify_keys
105+
field_reordered_start = {}
106+
field_reordered_start['field'] = field['field']
107+
field_reordered_start['type'] = field['type']
108+
109+
field.delete('field')
110+
field.delete('type')
111+
112+
field = reorder_keys_basic(field || {})
113+
114+
field_reordered_start.merge(field)
115+
end
116+
end
117+
end

lib/forest_liana/bootstraper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def send_apimap
175175
})
176176

177177
begin
178+
apimap = ForestLiana::ApimapSorter.new(apimap).perform
178179
uri = URI.parse("#{forest_url}/forest/apimaps")
179180
http = Net::HTTP.new(uri.host, uri.port)
180181
http.use_ssl = true if forest_url.start_with?('https')

spec/apimap_sorter_spec.rb

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
describe ForestLiana::ApimapSorter do
2+
describe 'apimap reordering' do
3+
context 'on a disordered apimap' do
4+
apimap = {
5+
'meta': {
6+
'orm_version': '4.34.9',
7+
'liana_version': '1.5.24',
8+
'database_type': 'postgresql',
9+
liana: 'forest-rails',
10+
},
11+
'data': [{
12+
id: 'users',
13+
type: 'collections',
14+
attributes: {
15+
fields: [
16+
{ field: 'id', type: 'Number' },
17+
{ field: 'name', type: 'String' },
18+
{ field: 'firstName', type: 'String' },
19+
{ field: 'lastName', type: 'String' },
20+
{ field: 'email', type: 'String' },
21+
{ field: 'url', type: 'String' },
22+
{ field: 'createdAt', type: 'Date' },
23+
{ field: 'updatedAt', type: 'Date' },
24+
],
25+
name: 'users',
26+
}
27+
}, {
28+
id: 'guests',
29+
type: 'collections',
30+
attributes: {
31+
fields: [
32+
{ field: 'id', type: 'Number' },
33+
{ field: 'email', type: 'String' },
34+
{ field: 'createdAt', type: 'Date' },
35+
{ field: 'updatedAt', type: 'Date' },
36+
],
37+
name: 'guests',
38+
}
39+
}, {
40+
type: 'collections',
41+
id: 'animals',
42+
attributes: {
43+
fields: [
44+
{ 'is-sortable': false, field: 'id', 'is-filterable': false, type: 'Number' },
45+
{ type: 'Date', field: 'createdAt' },
46+
{ field: 'updatedAt', type: 'Date' },
47+
],
48+
name: 'animals',
49+
integration: 'close.io',
50+
'is-virtual': true,
51+
}
52+
}],
53+
'included': [{
54+
id: 'users.Women',
55+
type: 'segments',
56+
attributes: {
57+
name: 'Women'
58+
}
59+
}, {
60+
id: 'users.import',
61+
type: 'actions',
62+
links: {
63+
self: '/actions'
64+
},
65+
attributes: {
66+
name: 'import',
67+
fields: [{
68+
isRequired: true,
69+
type: 'Boolean',
70+
field: 'Save',
71+
description: 'save the import file if true.',
72+
defaultValue: 'true'
73+
}, {
74+
type: 'File',
75+
field: 'File'
76+
}],
77+
'http-method': nil
78+
}
79+
}, {
80+
attributes: {
81+
name: 'Men'
82+
},
83+
id: 'users.Men',
84+
type: 'segments'
85+
}, {
86+
id: 'animals.ban',
87+
type: 'actions',
88+
links: {
89+
self: '/actions'
90+
},
91+
attributes: {
92+
name: 'import',
93+
global: true,
94+
download: nil,
95+
endpoint: nil,
96+
redirect: nil,
97+
'http-method': nil
98+
}
99+
}]
100+
}
101+
102+
apimap = ActiveSupport::JSON.encode(apimap)
103+
apimap = ActiveSupport::JSON.decode(apimap)
104+
apimap_sorted = ForestLiana::ApimapSorter.new(apimap).perform
105+
106+
it 'should sort the apimap sections' do
107+
expect(apimap_sorted.keys).to eq(['data', 'included', 'meta'])
108+
end
109+
110+
it 'should sort the data collections' do
111+
expect(apimap_sorted['data'].map { |collection| collection['id'] }).to eq(
112+
['animals', 'guests', 'users'])
113+
end
114+
115+
it 'should sort the data collection values' do
116+
expect(apimap_sorted['data'][0].keys).to eq(['type', 'id', 'attributes'])
117+
expect(apimap_sorted['data'][1].keys).to eq(['type', 'id', 'attributes'])
118+
expect(apimap_sorted['data'][2].keys).to eq(['type', 'id', 'attributes'])
119+
end
120+
121+
it 'should sort the data collections attributes values' do
122+
expect(apimap_sorted['data'][0]['attributes'].keys).to eq(['name', 'integration', 'is-virtual', 'fields'])
123+
expect(apimap_sorted['data'][1]['attributes'].keys).to eq(['name', 'fields'])
124+
expect(apimap_sorted['data'][2]['attributes'].keys).to eq(['name', 'fields'])
125+
end
126+
127+
it 'should sort the data collections attributes fields by name' do
128+
expect(apimap_sorted['data'][0]['attributes']['fields'].map { |field| field['field'] }).to eq(['createdAt', 'id', 'updatedAt'])
129+
expect(apimap_sorted['data'][1]['attributes']['fields'].map { |field| field['field'] }).to eq(['createdAt', 'email', 'id', 'updatedAt'])
130+
expect(apimap_sorted['data'][2]['attributes']['fields'].map { |field| field['field'] }).to eq(['createdAt', 'email', 'firstName', 'id', 'lastName', 'name', 'updatedAt', 'url'])
131+
end
132+
133+
it 'should sort the data collections attributes fields values' do
134+
expect(apimap_sorted['data'][0]['attributes']['fields'][1].keys).to eq(['field', 'type', 'is-filterable', 'is-sortable'])
135+
end
136+
137+
it 'should sort the included actions and segments objects' do
138+
expect(apimap_sorted['included'].map { |object| object['id'] }).to eq(
139+
['animals.ban', 'users.import', 'users.Men', 'users.Women'])
140+
end
141+
142+
it 'should sort the included actions and segments objects values' do
143+
expect(apimap_sorted['included'][0].keys).to eq(['type', 'id', 'attributes', 'links'])
144+
expect(apimap_sorted['included'][1].keys).to eq(['type', 'id', 'attributes', 'links'])
145+
expect(apimap_sorted['included'][2].keys).to eq(['type', 'id', 'attributes'])
146+
expect(apimap_sorted['included'][3].keys).to eq(['type', 'id', 'attributes'])
147+
end
148+
149+
it 'should sort the included actions and segments objects attributes values' do
150+
expect(apimap_sorted['included'][0]['attributes'].keys).to eq(['name', 'download', 'endpoint', 'global', 'http-method', 'redirect'])
151+
expect(apimap_sorted['included'][1]['attributes'].keys).to eq(['name', 'http-method', 'fields'])
152+
expect(apimap_sorted['included'][2]['attributes'].keys).to eq(['name'])
153+
expect(apimap_sorted['included'][3]['attributes'].keys).to eq(['name'])
154+
end
155+
156+
it 'should sort the included action attributes fields by name' do
157+
expect(apimap_sorted['included'][1]['attributes']['fields'].map { |field| field['field'] }).to eq(['File', 'Save'])
158+
end
159+
160+
it 'should sort the included action fields values' do
161+
expect(apimap_sorted['included'][1]['attributes']['fields'][1].keys).to eq(['field', 'type', 'defaultValue', 'description', 'isRequired'])
162+
end
163+
164+
it 'should sort the meta values' do
165+
expect(apimap_sorted['meta'].keys).to eq(
166+
['database_type', 'liana', 'liana_version', 'orm_version'])
167+
end
168+
end
169+
end
170+
end

0 commit comments

Comments
 (0)