forest_liana 9.3.16 → 9.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/actions_controller.rb +9 -4
- data/app/models/forest_liana/model/action.rb +3 -1
- data/app/services/forest_liana/apimap_sorter.rb +2 -0
- data/app/services/forest_liana/smart_action_form_parser.rb +69 -0
- data/lib/forest_liana/schema_file_updater.rb +2 -0
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/lib/forest_liana/collections/island.rb +83 -0
- data/spec/requests/actions_controller_spec.rb +53 -0
- data/spec/services/forest_liana/smart_action_form_parser_spec.rb +55 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 855ec9302b2f8b3c54c16efb2919b9324c22feecaa132a02075914478ae3b040
|
4
|
+
data.tar.gz: eb8275f28303c3b76a95460854037be8f619a8fa74541a91dd80128fb3e49457
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e140338ed979e85df67414887d3fc986b0eb58abe88649dcdc140b565f43b5e72f788d3952213e3c82ff5057b23b5c63bd3543be7279f534f77893efc3b1179
|
7
|
+
data.tar.gz: ec56a023d16948a3a21736b43ae89f3ab0cf5a9bec456b2a34d90299ceefdf9b0e0d9809d938e40f88e8bae30d7ce96cc63f8334ab0124bd95943d730e3aa999
|
@@ -52,12 +52,14 @@ module ForestLiana
|
|
52
52
|
return render status: 500, json: { error: 'Error in smart action load hook: hook must return an array of fields' }
|
53
53
|
end
|
54
54
|
|
55
|
+
result = SmartActionFormParser.extract_fields_and_layout(result)
|
56
|
+
|
55
57
|
# Validate that the fields are well formed.
|
56
58
|
begin
|
57
59
|
# action.hooks[:change] is a hashmap here
|
58
60
|
# to do the validation, only the hook names are require
|
59
61
|
change_hooks_name = action.hooks[:change].nil? ? nil : action.hooks[:change].keys
|
60
|
-
ForestLiana::SmartActionFieldValidator.validate_smart_action_fields(result, action.name, change_hooks_name)
|
62
|
+
ForestLiana::SmartActionFieldValidator.validate_smart_action_fields(result[:fields], action.name, change_hooks_name)
|
61
63
|
rescue ForestLiana::Errors::SmartActionInvalidFieldError => invalid_field_error
|
62
64
|
FOREST_LOGGER.warn invalid_field_error.message
|
63
65
|
rescue ForestLiana::Errors::SmartActionInvalidFieldHookError => invalid_hook_error
|
@@ -67,8 +69,8 @@ module ForestLiana
|
|
67
69
|
end
|
68
70
|
|
69
71
|
# Apply result on fields (transform the object back to an array), preserve order.
|
70
|
-
fields = result.map do |field|
|
71
|
-
updated_field = result.find{|f| f[:field] == field[:field]}
|
72
|
+
fields = result[:fields].map do |field|
|
73
|
+
updated_field = result[:fields].find{|f| f[:field] == field[:field]}
|
72
74
|
|
73
75
|
# Reset `value` when not present in `enums` (which means `enums` has changed).
|
74
76
|
if updated_field[:enums].is_a?(Array)
|
@@ -88,7 +90,10 @@ module ForestLiana
|
|
88
90
|
updated_field.transform_keys { |key| key.to_s.camelize(:lower) }
|
89
91
|
end
|
90
92
|
|
91
|
-
|
93
|
+
response = { fields: fields }
|
94
|
+
response[:layout] = result[:layout] unless result[:layout].all? { |element| element[:component] == 'input' }
|
95
|
+
|
96
|
+
render serializer: nil, json: response, status: :ok
|
92
97
|
end
|
93
98
|
|
94
99
|
def load
|
@@ -5,7 +5,7 @@ class ForestLiana::Model::Action
|
|
5
5
|
extend ActiveModel::Naming
|
6
6
|
|
7
7
|
attr_accessor :id, :name, :base_url, :endpoint, :http_method, :fields, :redirect,
|
8
|
-
:type, :download, :hooks
|
8
|
+
:type, :download, :hooks, :description, :submit_button_label
|
9
9
|
|
10
10
|
def initialize(attributes = {})
|
11
11
|
if attributes.key?(:global)
|
@@ -74,6 +74,8 @@ class ForestLiana::Model::Action
|
|
74
74
|
@type ||= "bulk"
|
75
75
|
@download ||= false
|
76
76
|
@hooks = !@hooks.nil? ? @hooks.symbolize_keys : nil
|
77
|
+
@description ||= nil
|
78
|
+
@submit_button_label ||= nil
|
77
79
|
end
|
78
80
|
|
79
81
|
def persisted?
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class SmartActionFormParser
|
3
|
+
def self.extract_fields_and_layout(form)
|
4
|
+
fields = []
|
5
|
+
layout = []
|
6
|
+
form&.each do |element|
|
7
|
+
if element[:type] == 'Layout'
|
8
|
+
validate_layout_element(element)
|
9
|
+
element[:component] = element[:component].camelize(:lower)
|
10
|
+
if %w[page row].include?(element[:component])
|
11
|
+
extract = extract_fields_and_layout_for_component(element)
|
12
|
+
layout << element
|
13
|
+
fields.concat(extract[:fields])
|
14
|
+
else
|
15
|
+
layout << element
|
16
|
+
end
|
17
|
+
else
|
18
|
+
fields << element
|
19
|
+
# frontend rule
|
20
|
+
layout << { component: 'input', fieldId: element[:field] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
{ fields: fields, layout: layout }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.extract_fields_and_layout_for_component(element)
|
28
|
+
# 'page' is in camel case because at this step the 'component' attribute is already convert for the response
|
29
|
+
key = element[:component] == 'page' ? :elements : :fields
|
30
|
+
extract = extract_fields_and_layout(element[key])
|
31
|
+
element[key] = extract[:layout]
|
32
|
+
|
33
|
+
extract
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.validate_layout_element(element)
|
37
|
+
valid_components = %w[Page Row Separator HtmlBlock]
|
38
|
+
unless valid_components.include?(element[:component])
|
39
|
+
raise ForestLiana::Errors::HTTP422Error.new(
|
40
|
+
"#{element[:component]} is not a valid component. Valid components are #{valid_components.join(' or ')}"
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
if element[:component] == 'Page'
|
45
|
+
unless element[:elements].is_a? Array
|
46
|
+
raise ForestLiana::Errors::HTTP422Error.new(
|
47
|
+
"Page components must contain an array of fields or layout elements in property 'elements'"
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
if element[:elements].any? { |element| element[:component] === 'Page' }
|
52
|
+
raise ForestLiana::Errors::HTTP422Error.new('Pages cannot contain other pages')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if element[:component] == 'Row'
|
57
|
+
unless element[:fields].is_a? Array
|
58
|
+
raise ForestLiana::Errors::HTTP422Error.new(
|
59
|
+
"Row components must contain an array of fields in property 'fields'"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
if element[:fields].any? { |element| element[:type] === 'Layout' }
|
64
|
+
raise ForestLiana::Errors::HTTP422Error.new('Row components can only contain fields')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/forest_liana/version.rb
CHANGED
@@ -43,6 +43,89 @@ class Forest::Island
|
|
43
43
|
}
|
44
44
|
}
|
45
45
|
|
46
|
+
action 'my_action_with_layout',
|
47
|
+
fields: [foo],
|
48
|
+
hooks: {
|
49
|
+
:load => -> (context) {
|
50
|
+
[
|
51
|
+
{
|
52
|
+
type: 'Layout',
|
53
|
+
component: 'Page',
|
54
|
+
elements: [
|
55
|
+
{
|
56
|
+
type: 'Layout',
|
57
|
+
component: 'HtmlBlock',
|
58
|
+
content: '<p>test</p>',
|
59
|
+
},
|
60
|
+
{
|
61
|
+
type: 'Layout',
|
62
|
+
component: 'Separator',
|
63
|
+
},
|
64
|
+
foo,
|
65
|
+
{
|
66
|
+
field: 'field 1',
|
67
|
+
type: 'String',
|
68
|
+
},
|
69
|
+
{
|
70
|
+
type: 'Layout',
|
71
|
+
component: 'Separator',
|
72
|
+
},
|
73
|
+
{
|
74
|
+
field: 'field 2',
|
75
|
+
type: 'String',
|
76
|
+
}
|
77
|
+
]
|
78
|
+
},
|
79
|
+
]
|
80
|
+
},
|
81
|
+
:change => {
|
82
|
+
'on_foo_changed' => -> (context) {
|
83
|
+
[
|
84
|
+
{
|
85
|
+
type: 'Layout',
|
86
|
+
component: 'Page',
|
87
|
+
elements: [
|
88
|
+
{
|
89
|
+
type: 'Layout',
|
90
|
+
component: 'HtmlBlock',
|
91
|
+
content: '<div style="text-align:center;">
|
92
|
+
<p>
|
93
|
+
<strong>Hi #{ctx.form_values["firstName"]} #{ctx.form_values["lastName"]}</strong>,
|
94
|
+
<br/>here you can put
|
95
|
+
<strong style="color: red;">all the html</strong> you want.
|
96
|
+
</p>
|
97
|
+
</div>
|
98
|
+
<div style="display: flex; flex-flow: row wrap; justify-content: space-around;">
|
99
|
+
<a href="https://www.w3schools.com" target="_blank">
|
100
|
+
<img src="https://www.w3schools.com/html/w3schools.jpg">
|
101
|
+
</a>
|
102
|
+
<iframe src="https://www.youtube.com/embed/xHPKuu9-yyw?autoplay=1&mute=1"></iframe>
|
103
|
+
</div>',
|
104
|
+
},
|
105
|
+
{
|
106
|
+
type: 'Layout',
|
107
|
+
component: 'Separator',
|
108
|
+
},
|
109
|
+
foo,
|
110
|
+
{
|
111
|
+
field: 'field 1',
|
112
|
+
type: 'String',
|
113
|
+
},
|
114
|
+
{
|
115
|
+
type: 'Layout',
|
116
|
+
component: 'Separator',
|
117
|
+
},
|
118
|
+
{
|
119
|
+
field: 'field 2',
|
120
|
+
type: 'String',
|
121
|
+
}
|
122
|
+
]
|
123
|
+
},
|
124
|
+
]
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
46
129
|
action 'fail_action',
|
47
130
|
fields: [foo],
|
48
131
|
hooks: {
|
@@ -41,6 +41,57 @@ describe 'Requesting Actions routes', :type => :request do
|
|
41
41
|
describe 'hooks' do
|
42
42
|
island = ForestLiana.apimap.find {|collection| collection.name.to_s == ForestLiana.name_for(Island)}
|
43
43
|
|
44
|
+
describe 'call /load on layout form' do
|
45
|
+
params = {
|
46
|
+
data: {
|
47
|
+
attributes: { ids: [1], collection_name: 'Island' }
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
it 'should respond 200 with expected response on load' do
|
52
|
+
post '/forest/actions/my_action_with_layout/hooks/load', params: JSON.dump(params), headers: headers
|
53
|
+
result = JSON.parse(response.body)
|
54
|
+
|
55
|
+
expect(response.status).to eq(200)
|
56
|
+
expect(result).to eq(
|
57
|
+
{
|
58
|
+
"fields" => [
|
59
|
+
{
|
60
|
+
"field"=>"foo",
|
61
|
+
"type"=>"String",
|
62
|
+
"defaultValue"=>nil,
|
63
|
+
"enums"=>nil,
|
64
|
+
"isRequired"=>false,
|
65
|
+
"isReadOnly"=>false,
|
66
|
+
"reference"=>nil,
|
67
|
+
"description"=>nil,
|
68
|
+
"hook"=>"on_foo_changed",
|
69
|
+
"position"=>0,
|
70
|
+
"widgetEdit"=>nil,
|
71
|
+
"value"=>nil
|
72
|
+
},
|
73
|
+
{ "field"=>"field 1", "type"=>"String"},
|
74
|
+
{"field"=>"field 2", "type"=>"String" }
|
75
|
+
],
|
76
|
+
"layout"=>[
|
77
|
+
{
|
78
|
+
"type"=>"Layout",
|
79
|
+
"component"=>"page",
|
80
|
+
"elements"=>[
|
81
|
+
{"type"=>"Layout", "component"=>"htmlBlock", "content"=>"<p>test</p>"},
|
82
|
+
{"type"=>"Layout", "component"=>"separator"},
|
83
|
+
{"component"=>"input", "fieldId"=>"foo"},
|
84
|
+
{"component"=>"input", "fieldId"=>"field 1"},
|
85
|
+
{"type"=>"Layout", "component"=>"separator"},
|
86
|
+
{"component"=>"input", "fieldId"=>"field 2"}
|
87
|
+
]
|
88
|
+
}
|
89
|
+
]
|
90
|
+
}
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
44
95
|
describe 'call /load' do
|
45
96
|
params = {
|
46
97
|
data: {
|
@@ -54,6 +105,8 @@ describe 'Requesting Actions routes', :type => :request do
|
|
54
105
|
foo = action.fields.select { |field| field[:field] == 'foo' }.first
|
55
106
|
expect(response.status).to eq(200)
|
56
107
|
expect(JSON.parse(response.body)).to eq({'fields' => [foo.merge({:value => nil}).transform_keys { |key| key.to_s.camelize(:lower) }.stringify_keys]})
|
108
|
+
# action form without layout elements should not have the key layout
|
109
|
+
expect(JSON.parse(response.body)).not_to have_key('layout')
|
57
110
|
end
|
58
111
|
|
59
112
|
it 'should respond 422 with bad params' do
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe SmartActionFormParser do
|
3
|
+
describe "self.validate_layout_element" do
|
4
|
+
it "raise an error with an invalid component" do
|
5
|
+
expect { SmartActionFormParser.validate_layout_element({ type: 'Layout', component: 'foo' }) }
|
6
|
+
.to raise_error(
|
7
|
+
ForestLiana::Errors::HTTP422Error,
|
8
|
+
'foo is not a valid component. Valid components are Page or Row or Separator or HtmlBlock'
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "raise an error with an invalid Page" do
|
13
|
+
expect do
|
14
|
+
SmartActionFormParser.validate_layout_element(
|
15
|
+
{ type: 'Layout', component: 'Page', elements: 'foo' }
|
16
|
+
)
|
17
|
+
end.to raise_error(
|
18
|
+
ForestLiana::Errors::HTTP422Error,
|
19
|
+
"Page components must contain an array of fields or layout elements in property 'elements'"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raise an error with a Page that contains page" do
|
24
|
+
expect do
|
25
|
+
SmartActionFormParser.validate_layout_element(
|
26
|
+
{ type: 'Layout', component: 'Page', elements: [{ type: 'Layout', component: 'Page', elements: [] }] }
|
27
|
+
)
|
28
|
+
end.to raise_error(ForestLiana::Errors::HTTP422Error, 'Pages cannot contain other pages')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise an error with an invalid Row" do
|
32
|
+
expect do
|
33
|
+
SmartActionFormParser.validate_layout_element(
|
34
|
+
{ type: 'Layout', component: 'Row', fields: 'foo' }
|
35
|
+
)
|
36
|
+
end.to raise_error(
|
37
|
+
ForestLiana::Errors::HTTP422Error,
|
38
|
+
"Row components must contain an array of fields in property 'fields'"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raise an error with a row that contains layout element" do
|
43
|
+
expect do
|
44
|
+
SmartActionFormParser.validate_layout_element(
|
45
|
+
{
|
46
|
+
type: 'Layout',
|
47
|
+
component: 'Row',
|
48
|
+
fields: [ { type: 'Layout', component: 'HtmlBlock', fields: 'Row components can only contain fields' }]
|
49
|
+
}
|
50
|
+
)
|
51
|
+
end.to raise_error(ForestLiana::Errors::HTTP422Error, 'Row components can only contain fields')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_liana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.
|
4
|
+
version: 9.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Munda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -305,6 +305,7 @@ files:
|
|
305
305
|
- app/services/forest_liana/scope_manager.rb
|
306
306
|
- app/services/forest_liana/search_query_builder.rb
|
307
307
|
- app/services/forest_liana/smart_action_field_validator.rb
|
308
|
+
- app/services/forest_liana/smart_action_form_parser.rb
|
308
309
|
- app/services/forest_liana/stat_getter.rb
|
309
310
|
- app/services/forest_liana/stripe_base_getter.rb
|
310
311
|
- app/services/forest_liana/stripe_invoice_getter.rb
|
@@ -441,6 +442,7 @@ files:
|
|
441
442
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
442
443
|
- spec/services/forest_liana/scope_manager_spec.rb
|
443
444
|
- spec/services/forest_liana/smart_action_field_validator_spec.rb
|
445
|
+
- spec/services/forest_liana/smart_action_form_parser_spec.rb
|
444
446
|
- spec/services/forest_liana/utils/context_variables_injector_spec.rb
|
445
447
|
- spec/services/forest_liana/utils/context_variables_spec.rb
|
446
448
|
- spec/services/forest_liana/value_stat_getter_spec.rb
|
@@ -741,6 +743,7 @@ test_files:
|
|
741
743
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
742
744
|
- spec/services/forest_liana/scope_manager_spec.rb
|
743
745
|
- spec/services/forest_liana/smart_action_field_validator_spec.rb
|
746
|
+
- spec/services/forest_liana/smart_action_form_parser_spec.rb
|
744
747
|
- spec/services/forest_liana/utils/context_variables_injector_spec.rb
|
745
748
|
- spec/services/forest_liana/utils/context_variables_spec.rb
|
746
749
|
- spec/services/forest_liana/value_stat_getter_spec.rb
|