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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +20 -18
  3. data/app/controllers/forest_liana/application_controller.rb +0 -9
  4. data/app/controllers/forest_liana/associations_controller.rb +2 -2
  5. data/app/controllers/forest_liana/resources_controller.rb +15 -6
  6. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  7. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -3
  8. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  9. data/app/services/forest_liana/filters_parser.rb +8 -4
  10. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  11. data/app/services/forest_liana/has_many_getter.rb +2 -2
  12. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  13. data/app/services/forest_liana/line_stat_getter.rb +4 -2
  14. data/app/services/forest_liana/permissions_checker.rb +3 -4
  15. data/app/services/forest_liana/permissions_getter.rb +2 -2
  16. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  17. data/app/services/forest_liana/resource_getter.rb +6 -3
  18. data/app/services/forest_liana/resource_updater.rb +5 -2
  19. data/app/services/forest_liana/resources_getter.rb +6 -5
  20. data/app/services/forest_liana/scope_manager.rb +102 -0
  21. data/app/services/forest_liana/search_query_builder.rb +6 -3
  22. data/app/services/forest_liana/stat_getter.rb +2 -1
  23. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  24. data/config/routes.rb +3 -1
  25. data/lib/forest_liana/version.rb +1 -1
  26. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  27. data/spec/dummy/config/routes.rb +4 -0
  28. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  29. data/spec/requests/actions_controller_spec.rb +144 -23
  30. data/spec/requests/resources_spec.rb +2 -0
  31. data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
  32. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  33. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  34. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +1 -3
  35. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  36. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  37. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  38. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  39. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  40. metadata +20 -15
  41. data/app/services/forest_liana/scope_validator.rb +0 -98
  42. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  43. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  44. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  45. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  46. 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