forest_liana 7.0.0.beta.3 → 7.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/app/controllers/forest_liana/actions_controller.rb +20 -18
- data/app/controllers/forest_liana/application_controller.rb +0 -9
- data/app/controllers/forest_liana/associations_controller.rb +2 -2
- data/app/controllers/forest_liana/resources_controller.rb +15 -6
- data/app/controllers/forest_liana/scopes_controller.rb +20 -0
- data/app/controllers/forest_liana/smart_actions_controller.rb +39 -3
- data/app/controllers/forest_liana/stats_controller.rb +5 -5
- data/app/services/forest_liana/filters_parser.rb +8 -4
- data/app/services/forest_liana/has_many_dissociator.rb +2 -2
- data/app/services/forest_liana/has_many_getter.rb +2 -2
- data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
- data/app/services/forest_liana/line_stat_getter.rb +4 -2
- data/app/services/forest_liana/permissions_checker.rb +3 -4
- data/app/services/forest_liana/permissions_getter.rb +2 -2
- data/app/services/forest_liana/pie_stat_getter.rb +6 -3
- data/app/services/forest_liana/resource_getter.rb +6 -3
- data/app/services/forest_liana/resource_updater.rb +5 -2
- data/app/services/forest_liana/resources_getter.rb +6 -5
- data/app/services/forest_liana/scope_manager.rb +102 -0
- data/app/services/forest_liana/search_query_builder.rb +6 -3
- data/app/services/forest_liana/stat_getter.rb +2 -1
- data/app/services/forest_liana/value_stat_getter.rb +4 -2
- data/config/routes.rb +3 -1
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
- data/spec/requests/actions_controller_spec.rb +144 -23
- data/spec/requests/resources_spec.rb +2 -0
- data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
- data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
- data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
- data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +1 -3
- data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
- data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
- data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
- data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
- data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
- metadata +20 -15
- data/app/services/forest_liana/scope_validator.rb +0 -98
- data/test/services/forest_liana/has_many_getter_test.rb +0 -75
- data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
- data/test/services/forest_liana/resource_updater_test.rb +0 -86
- data/test/services/forest_liana/scope_validator_test.rb +0 -185
- data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
@@ -17,6 +17,8 @@ describe 'Requesting Tree resources', :type => :request do
|
|
17
17
|
allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
|
18
18
|
|
19
19
|
allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { true }
|
20
|
+
|
21
|
+
allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return({})
|
20
22
|
end
|
21
23
|
|
22
24
|
token = JWT.encode({
|
@@ -281,7 +281,7 @@ module ForestLiana
|
|
281
281
|
let(:result) { filter_parser.parse_condition(condition) }
|
282
282
|
|
283
283
|
context 'on valid condition' do
|
284
|
-
it { expect(result).to eq "\"trees\".\"name\" LIKE '%3'" }
|
284
|
+
it { expect(result).to eq "\"trees\".\"name\" LIKE ('%3')" }
|
285
285
|
end
|
286
286
|
|
287
287
|
context 'on belongs_to condition' do
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe HasManyGetter do
|
3
|
+
describe 'when retrieving has many relationship related records' do
|
4
|
+
let(:rendering_id) { 13 }
|
5
|
+
let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
|
6
|
+
let(:scopes) { { } }
|
7
|
+
let(:association) { Island.reflect_on_association(:trees) }
|
8
|
+
let(:params) {
|
9
|
+
{
|
10
|
+
id: Island.first.id,
|
11
|
+
association_name: 'trees',
|
12
|
+
page: { size: 15, number: 1 },
|
13
|
+
timezone: 'America/Nome'
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
subject {
|
18
|
+
described_class.new(Island, association, params, user)
|
19
|
+
}
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
madagascar = Island.create(name: 'madagascar')
|
23
|
+
re = Island.create(name: 'ré')
|
24
|
+
Tree.create(name: 'lemon tree', island: madagascar)
|
25
|
+
Tree.create(name: 'banana tree', island: madagascar)
|
26
|
+
Tree.create(name: 'papaya tree', island: madagascar)
|
27
|
+
Tree.create(name: 'apple tree', island: re)
|
28
|
+
Tree.create(name: 'banana tree', island: re)
|
29
|
+
ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
|
30
|
+
allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:each) do
|
34
|
+
Island.destroy_all
|
35
|
+
Tree.destroy_all
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'with empty scopes' do
|
39
|
+
describe 'with page 1 size 15' do
|
40
|
+
it 'should return the 3 trees matching madagascar' do
|
41
|
+
subject.perform
|
42
|
+
|
43
|
+
expect(subject.records.count).to eq 3
|
44
|
+
expect(subject.count).to eq 3
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'when sorting by decreasing id' do
|
49
|
+
let(:params) {
|
50
|
+
{
|
51
|
+
id: Island.first.id,
|
52
|
+
association_name: 'trees',
|
53
|
+
sort: '-id',
|
54
|
+
page: { size: 15, number: 1 },
|
55
|
+
timezone: 'America/Nome'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
it 'should order records properly' do
|
60
|
+
subject.perform
|
61
|
+
|
62
|
+
expect(subject.records.count).to eq 3
|
63
|
+
expect(subject.count).to eq 3
|
64
|
+
expect(subject.records.first.id).to be > subject.records.last.id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'when searching for banana tree' do
|
69
|
+
let(:params) {
|
70
|
+
{
|
71
|
+
id: Island.first.id,
|
72
|
+
association_name: 'trees',
|
73
|
+
search: 'banana',
|
74
|
+
page: { size: 15, number: 1 },
|
75
|
+
timezone: 'America/Nome'
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
it 'should return only the banana tree linked to madagascar' do
|
80
|
+
subject.perform
|
81
|
+
|
82
|
+
expect(subject.records.count).to eq 1
|
83
|
+
expect(subject.count).to eq 1
|
84
|
+
expect(subject.records.first.island.name).to eq 'madagascar'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'with scopes' do
|
90
|
+
let(:scopes) { {
|
91
|
+
'Tree' => {
|
92
|
+
'scope'=> {
|
93
|
+
'filter'=> {
|
94
|
+
'aggregator' => 'and',
|
95
|
+
'conditions' => [
|
96
|
+
{ 'field' => 'name', 'operator' => 'contains', 'value' => 'a' }
|
97
|
+
]
|
98
|
+
},
|
99
|
+
'dynamicScopesValues' => { }
|
100
|
+
}
|
101
|
+
}
|
102
|
+
} }
|
103
|
+
|
104
|
+
describe 'when asking for all trees related to madagascar' do
|
105
|
+
it 'should return trees belonging to madagascar and matching the scopes' do
|
106
|
+
subject.perform
|
107
|
+
|
108
|
+
# Only `papaya` and `banana` contain an `a`
|
109
|
+
expect(subject.records.count).to eq 2
|
110
|
+
expect(subject.count).to eq 2
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -1,12 +1,21 @@
|
|
1
1
|
module ForestLiana
|
2
2
|
describe LineStatGetter do
|
3
|
+
let(:rendering_id) { 13 }
|
4
|
+
let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
|
5
|
+
let(:scopes) { { } }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
|
9
|
+
allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
|
10
|
+
end
|
11
|
+
|
3
12
|
describe 'Check client_timezone function' do
|
4
13
|
describe 'with a SQLite database' do
|
5
14
|
it 'should return false' do
|
6
15
|
expect(LineStatGetter.new(Owner, {
|
7
16
|
timezone: "Europe/Paris",
|
8
17
|
aggregate: "Count",
|
9
|
-
}).client_timezone).to eq(false)
|
18
|
+
}, user).client_timezone).to eq(false)
|
10
19
|
end
|
11
20
|
end
|
12
21
|
|
@@ -16,7 +25,7 @@ module ForestLiana
|
|
16
25
|
expect(LineStatGetter.new(Owner, {
|
17
26
|
timezone: "Europe/Paris",
|
18
27
|
aggregate: "Count",
|
19
|
-
}).client_timezone).to eq('Europe/Paris')
|
28
|
+
}, user).client_timezone).to eq('Europe/Paris')
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
@@ -25,7 +34,6 @@ module ForestLiana
|
|
25
34
|
describe 'Using a Count aggregation' do
|
26
35
|
describe 'Using a Week time range' do
|
27
36
|
it 'should return consistent data based on monday as week_start ' do
|
28
|
-
|
29
37
|
# Week should start on monday
|
30
38
|
# 08-05-2021 was a Saturday
|
31
39
|
Owner.create(name: 'Michel', hired_at: Date.parse('08-05-2021'));
|
@@ -38,8 +46,8 @@ module ForestLiana
|
|
38
46
|
aggregate: "Count",
|
39
47
|
time_range: "Week",
|
40
48
|
group_by_date_field: "hired_at",
|
41
|
-
}).perform
|
42
|
-
|
49
|
+
}, user).perform
|
50
|
+
|
43
51
|
expect(stat.value.find { |item| item[:label] == "W18-2021" }[:values][:value]).to eq(2)
|
44
52
|
expect(stat.value.find { |item| item[:label] == "W19-2021" }[:values][:value]).to eq(2)
|
45
53
|
end
|
@@ -47,4 +55,4 @@ module ForestLiana
|
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
50
|
-
end
|
58
|
+
end
|
@@ -191,7 +191,6 @@ module ForestLiana
|
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
-
|
195
194
|
context 'renderings cache' do
|
196
195
|
let(:fake_ressource) { collection_name }
|
197
196
|
let(:rendering_id) { 1 }
|
@@ -499,7 +498,7 @@ module ForestLiana
|
|
499
498
|
expect(subject.is_authorized?).to be false
|
500
499
|
end
|
501
500
|
end
|
502
|
-
|
501
|
+
|
503
502
|
end
|
504
503
|
|
505
504
|
context 'when user has not the required permission' do
|
@@ -519,7 +518,6 @@ module ForestLiana
|
|
519
518
|
expect(subject.is_authorized?).to be false
|
520
519
|
end
|
521
520
|
end
|
522
|
-
|
523
521
|
end
|
524
522
|
end
|
525
523
|
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe PieStatGetter do
|
3
|
+
let(:rendering_id) { 13 }
|
4
|
+
let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
|
5
|
+
let(:records) { [
|
6
|
+
{ name: 'Young Tree n1', age: 3 },
|
7
|
+
{ name: 'Young Tree n2', age: 3 },
|
8
|
+
{ name: 'Young Tree n3', age: 3 },
|
9
|
+
{ name: 'Young Tree n4', age: 3 },
|
10
|
+
{ name: 'Young Tree n5', age: 3 },
|
11
|
+
{ name: 'Old Tree n1', age: 15 },
|
12
|
+
{ name: 'Old Tree n2', age: 15 },
|
13
|
+
{ name: 'Old Tree n3', age: 15 },
|
14
|
+
{ name: 'Old Tree n4', age: 15 }
|
15
|
+
] }
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
|
19
|
+
allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
|
20
|
+
|
21
|
+
records.each { |record|
|
22
|
+
Tree.create!(name: record[:name], age: record[:age])
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:model) { Tree }
|
27
|
+
let(:collection) { 'trees' }
|
28
|
+
let(:params) {
|
29
|
+
{
|
30
|
+
type: 'Pie',
|
31
|
+
collection: collection,
|
32
|
+
timezone: 'Europe/Paris',
|
33
|
+
aggregate: 'Count',
|
34
|
+
group_by_field: group_by_field
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
subject { PieStatGetter.new(model, params, user) }
|
39
|
+
|
40
|
+
describe 'with empty scopes' do
|
41
|
+
let(:scopes) { { } }
|
42
|
+
|
43
|
+
describe 'with an aggregate on the name field' do
|
44
|
+
let(:group_by_field) { 'name' }
|
45
|
+
|
46
|
+
it 'should be as many categories as records count' do
|
47
|
+
subject.perform
|
48
|
+
expect(subject.record.value).to eq [
|
49
|
+
{:key => "Old Tree n1", :value => 1},
|
50
|
+
{:key => "Old Tree n2", :value => 1},
|
51
|
+
{:key => "Old Tree n3", :value => 1},
|
52
|
+
{:key => "Old Tree n4", :value => 1},
|
53
|
+
{:key => "Young Tree n1", :value => 1},
|
54
|
+
{:key => "Young Tree n2", :value => 1},
|
55
|
+
{:key => "Young Tree n3", :value => 1},
|
56
|
+
{:key => "Young Tree n4", :value => 1},
|
57
|
+
{:key => "Young Tree n5", :value => 1}
|
58
|
+
]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'with an aggregate on the age field' do
|
63
|
+
let(:group_by_field) { 'age' }
|
64
|
+
|
65
|
+
it 'should be as many categories as different ages among records' do
|
66
|
+
subject.perform
|
67
|
+
expect(subject.record.value).to eq [{ :key => 3, :value => 5}, { :key => 15, :value => 4 }]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'with scopes' do
|
73
|
+
let(:scopes) {
|
74
|
+
{
|
75
|
+
'Tree' => {
|
76
|
+
'scope'=> {
|
77
|
+
'filter'=> {
|
78
|
+
'aggregator' => 'and',
|
79
|
+
'conditions' => [
|
80
|
+
{ 'field' => 'age', 'operator' => 'less_than', 'value' => 10 }
|
81
|
+
]
|
82
|
+
},
|
83
|
+
'dynamicScopesValues' => { }
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
describe 'with an aggregate on the name field' do
|
90
|
+
let(:group_by_field) { 'name' }
|
91
|
+
|
92
|
+
it 'should be as many categories as records inside the scope' do
|
93
|
+
subject.perform
|
94
|
+
expect(subject.record.value).to eq [
|
95
|
+
{:key => "Young Tree n1", :value => 1},
|
96
|
+
{:key => "Young Tree n2", :value => 1},
|
97
|
+
{:key => "Young Tree n3", :value => 1},
|
98
|
+
{:key => "Young Tree n4", :value => 1},
|
99
|
+
{:key => "Young Tree n5", :value => 1}
|
100
|
+
]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'with an aggregate on the age field' do
|
105
|
+
let(:group_by_field) { 'age' }
|
106
|
+
|
107
|
+
it 'should be only one category' do
|
108
|
+
subject.perform
|
109
|
+
expect(subject.record.value).to eq [{ :key => 3, :value => 5}]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe ResourceUpdater do
|
3
|
+
describe 'when updating a record' do
|
4
|
+
let(:params) {
|
5
|
+
ActionController::Parameters.new(
|
6
|
+
id: 1,
|
7
|
+
data: {
|
8
|
+
id: 1,
|
9
|
+
type: 'User',
|
10
|
+
attributes: attributes
|
11
|
+
}
|
12
|
+
)
|
13
|
+
}
|
14
|
+
let(:rendering_id) { 13 }
|
15
|
+
let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
|
16
|
+
let(:scopes) { { } }
|
17
|
+
|
18
|
+
subject {
|
19
|
+
described_class.new(User, params, user)
|
20
|
+
}
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
User.create(name: 'Merry')
|
24
|
+
ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
|
25
|
+
allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
|
26
|
+
end
|
27
|
+
|
28
|
+
after(:each) do
|
29
|
+
User.destroy_all
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'with empty scopes' do
|
33
|
+
describe 'with a missing name in attributes' do
|
34
|
+
let(:attributes) { { } }
|
35
|
+
|
36
|
+
it 'should not update the record name' do
|
37
|
+
subject.perform
|
38
|
+
|
39
|
+
expect(subject.record.valid?).to be true
|
40
|
+
expect(subject.record.name).to eq 'Merry'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'with a null name in attributes' do
|
45
|
+
let(:attributes) { { name: nil } }
|
46
|
+
|
47
|
+
it 'should set the record name to null' do
|
48
|
+
subject.perform
|
49
|
+
|
50
|
+
expect(subject.record.valid?).to be true
|
51
|
+
expect(subject.record.name).to eq nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'with a new value as name in attributes' do
|
56
|
+
let(:attributes) { { name: 'Pippin' } }
|
57
|
+
|
58
|
+
it 'should set the record name to null' do
|
59
|
+
subject.perform
|
60
|
+
|
61
|
+
expect(subject.record.valid?).to be true
|
62
|
+
expect(subject.record.name).to eq 'Pippin'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'with scope excluding target record' do
|
68
|
+
let(:attributes) { { name: 'Gandalf' } }
|
69
|
+
let(:scopes) { {
|
70
|
+
'User' => {
|
71
|
+
'scope'=> {
|
72
|
+
'filter'=> {
|
73
|
+
'aggregator' => 'and',
|
74
|
+
'conditions' => [
|
75
|
+
{ 'field' => 'id', 'operator' => 'greater_than', 'value' => 2 }
|
76
|
+
]
|
77
|
+
},
|
78
|
+
'dynamicScopesValues' => { }
|
79
|
+
}
|
80
|
+
}
|
81
|
+
} }
|
82
|
+
|
83
|
+
it 'should not update the record name' do
|
84
|
+
subject.perform
|
85
|
+
|
86
|
+
expect(subject.record).to be nil
|
87
|
+
expect(subject.errors[0][:detail]).to eq 'Couldn\'t find User with \'id\'=1 [WHERE (("users"."id" > (2)))]'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'with scope including target record' do
|
92
|
+
let(:attributes) { { name: 'Gandalf' } }
|
93
|
+
let(:scopes) { {
|
94
|
+
'User' => {
|
95
|
+
'scope'=> {
|
96
|
+
'filter'=> {
|
97
|
+
'aggregator' => 'and',
|
98
|
+
'conditions' => [
|
99
|
+
{ 'field' => 'id', 'operator' => 'less_than', 'value' => 2 }
|
100
|
+
]
|
101
|
+
},
|
102
|
+
'dynamicScopesValues' => { }
|
103
|
+
}
|
104
|
+
}
|
105
|
+
} }
|
106
|
+
|
107
|
+
it 'should not update the record name' do
|
108
|
+
subject.perform
|
109
|
+
|
110
|
+
expect(subject.record.valid?).to be true
|
111
|
+
expect(subject.record.name).to eq 'Gandalf'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|