forest_liana 9.3.16 → 9.4.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 +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
|