forest_admin_agent 1.0.0.pre.beta.29 → 1.0.0.pre.beta.30
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/README.md +1 -1
- data/forest_admin_agent.gemspec +1 -1
- data/lib/forest_admin_agent/http/router.rb +9 -9
- data/lib/forest_admin_agent/routes/action/action.rb +137 -0
- data/lib/forest_admin_agent/utils/id.rb +4 -4
- data/lib/forest_admin_agent/utils/schema/action_fields.rb +27 -0
- data/lib/forest_admin_agent/utils/schema/forest_value_converter.rb +105 -0
- data/lib/forest_admin_agent/utils/schema/generator_action.rb +101 -0
- data/lib/forest_admin_agent/utils/schema/generator_collection.rb +9 -1
- data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +5 -3
- data/lib/forest_admin_agent/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c8589b7ac5d1648ad10bebc5e3e2d8f55b176a4804f44f0d1b7dce77a747646
|
4
|
+
data.tar.gz: bfbb688a7c3bd8acb6849379757bf9255ae65a4ce272611d724122332d7774dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 206fc75159915647d7a7f895439d7ceb4e58baceade060b08853f2fdfcc27ac6dcaab0b20ffd6902b3bc96be3bc8915a7aad9f16332a946a3109f0a5a53def21
|
7
|
+
data.tar.gz: 653dec755d3dc730b9f84404f3d22430e729732e2e31dbea46d6b06b3e2e069e8db9f1e7dffc4a77be6d04ff682bfa3f69963d281599fae4b45c402def701606
|
data/README.md
CHANGED
@@ -32,4 +32,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
|
|
32
32
|
|
33
33
|
## License
|
34
34
|
|
35
|
-
The gem is available as open source under the terms of the [
|
35
|
+
The gem is available as open source under the terms of the [GPL-3.0 License](https://www.gnu.org/licenses/gpl-3.0.en.html).
|
data/forest_admin_agent.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = "Ruby agent for Forest Admin."
|
13
13
|
spec.description = "Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
|
14
14
|
admin work on any Ruby application."
|
15
|
-
spec.license = "
|
15
|
+
spec.license = "GPL-3.0"
|
16
16
|
spec.required_ruby_version = ">= 3.0.0"
|
17
17
|
|
18
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -5,7 +5,7 @@ module ForestAdminAgent
|
|
5
5
|
|
6
6
|
def self.routes
|
7
7
|
[
|
8
|
-
|
8
|
+
actions_routes,
|
9
9
|
# api_charts_routes,
|
10
10
|
System::HealthCheck.new.routes,
|
11
11
|
Security::Authentication.new.routes,
|
@@ -25,14 +25,14 @@ module ForestAdminAgent
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.actions_routes
|
28
|
-
routes =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
routes
|
28
|
+
routes = {}
|
29
|
+
Facades::Container.datasource.collections.each_value do |collection|
|
30
|
+
collection.schema[:actions].each_key do |action_name|
|
31
|
+
routes.merge!(Action::Action.new(collection, action_name).routes)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
routes
|
36
36
|
end
|
37
37
|
|
38
38
|
def self.api_charts_routes
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'jsonapi-serializers'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Routes
|
6
|
+
module Action
|
7
|
+
class Action < AbstractAuthenticatedRoute
|
8
|
+
include ForestAdminAgent::Builder
|
9
|
+
include ForestAdminAgent::Utils
|
10
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
11
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
12
|
+
include ForestAdminDatasourceCustomizer::Decorators::Action
|
13
|
+
|
14
|
+
def initialize(collection, action)
|
15
|
+
@action_name = action
|
16
|
+
@collection = collection
|
17
|
+
super()
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup_routes
|
21
|
+
action_index = @collection.schema[:actions].keys.index(@action_name)
|
22
|
+
slug = ForestAdminAgent::Utils::Schema::GeneratorAction.get_action_slug(@action_name)
|
23
|
+
route_name = "forest_action_#{@collection.name}_#{action_index}_#{slug}"
|
24
|
+
path = "/_actions/:collection_name/#{action_index}/#{slug}"
|
25
|
+
|
26
|
+
add_route(route_name, 'post', path, proc { |args| handle_request(args) })
|
27
|
+
add_route(
|
28
|
+
"#{route_name}_load",
|
29
|
+
'post',
|
30
|
+
"#{path}/hooks/load",
|
31
|
+
proc { |args| handle_hook_request(args) }
|
32
|
+
)
|
33
|
+
add_route(
|
34
|
+
"#{route_name}_change",
|
35
|
+
'post',
|
36
|
+
"#{path}/hooks/change",
|
37
|
+
proc { |args| handle_hook_request(args) }
|
38
|
+
)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_request(args = {})
|
43
|
+
build(args)
|
44
|
+
filter_for_caller = get_record_selection(args)
|
45
|
+
get_record_selection(args, include_user_scope: false)
|
46
|
+
|
47
|
+
# TODO: permission
|
48
|
+
|
49
|
+
raw_data = args.dig(:params, :data, :attributes, :values)
|
50
|
+
|
51
|
+
# As forms are dynamic, we don't have any way to ensure that we're parsing the data correctly
|
52
|
+
# better send invalid data to the getForm() customer handler than to the execute() one.
|
53
|
+
unsafe_data = Schema::ForestValueConverter.make_form_data_unsafe(raw_data)
|
54
|
+
|
55
|
+
fields = @collection.get_form(
|
56
|
+
@caller,
|
57
|
+
@action_name,
|
58
|
+
unsafe_data,
|
59
|
+
filter_for_caller,
|
60
|
+
{ include_hidden_fields: true } # during execute, we need all possible fields
|
61
|
+
)
|
62
|
+
|
63
|
+
# Now that we have the field list, we can parse the data again.
|
64
|
+
data = Schema::ForestValueConverter.make_form_data(@datasource, raw_data, fields)
|
65
|
+
|
66
|
+
{ content: @collection.execute(@caller, @action_name, data, filter_for_caller) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_hook_request(args = {})
|
70
|
+
build(args)
|
71
|
+
forest_fields = args.dig(:params, :data, :attributes, :fields)
|
72
|
+
data = (Schema::ForestValueConverter.make_form_data_from_fields(@datasource, forest_fields) if forest_fields)
|
73
|
+
filter = get_record_selection(args)
|
74
|
+
|
75
|
+
fields = @collection.get_form(
|
76
|
+
@caller,
|
77
|
+
@action_name,
|
78
|
+
data,
|
79
|
+
filter,
|
80
|
+
{
|
81
|
+
change_field: nil,
|
82
|
+
search_field: nil,
|
83
|
+
search_values: {},
|
84
|
+
includeHiddenFields: false
|
85
|
+
}
|
86
|
+
)
|
87
|
+
|
88
|
+
{
|
89
|
+
content: {
|
90
|
+
fields: fields&.map { |field| Schema::GeneratorAction.build_field_schema(@datasource, field) } || {}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def get_record_selection(args, include_user_scope: true)
|
98
|
+
attributes = args.dig(:params, :data, :attributes)
|
99
|
+
|
100
|
+
# Match user filter + search + scope? + segment
|
101
|
+
scope = include_user_scope ? @permissions.get_scope(@collection) : nil
|
102
|
+
filter = Filter.new(
|
103
|
+
condition_tree: ConditionTreeFactory.intersect(
|
104
|
+
[
|
105
|
+
scope,
|
106
|
+
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(
|
107
|
+
@collection, args
|
108
|
+
)
|
109
|
+
]
|
110
|
+
)
|
111
|
+
)
|
112
|
+
|
113
|
+
# Restrict the filter to the selected records for single or bulk actions
|
114
|
+
if @collection.schema[:actions][@action_name].scope != Types::ActionScope::GLOBAL
|
115
|
+
selection_ids = Utils::Id.parse_selection_ids(@collection, args[:params])
|
116
|
+
selected_ids = ConditionTreeFactory.match_ids(@collection, selection_ids[:ids])
|
117
|
+
selected_ids = selected_ids.inverse if selection_ids[:are_excluded]
|
118
|
+
filter = filter.override(
|
119
|
+
condition_tree: ConditionTreeFactory.intersect([filter.condition_tree, selected_ids])
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Restrict the filter further for the "related data" page
|
124
|
+
unless attributes[:parent_association_name].nil?
|
125
|
+
relation = attributes[:parent_association_name]
|
126
|
+
parent = @datasource.get_collection(attributes[:parent_collection_name])
|
127
|
+
parent_id = Utils::Id.unpack_id(parent, attributes[:parent_collection_id])
|
128
|
+
|
129
|
+
filter = FilterFactory.make_foreign_filter(parent, parent_id, relation, @caller, filter)
|
130
|
+
end
|
131
|
+
|
132
|
+
filter
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -41,16 +41,16 @@ module ForestAdminAgent
|
|
41
41
|
|
42
42
|
def self.parse_selection_ids(collection, params, with_key: false)
|
43
43
|
attributes = begin
|
44
|
-
params.dig(
|
44
|
+
params.dig(:data, :attributes)
|
45
45
|
rescue StandardError
|
46
46
|
nil
|
47
47
|
end
|
48
48
|
|
49
|
-
are_excluded = attributes&.key?(
|
50
|
-
input_ids = attributes&.key?(
|
49
|
+
are_excluded = attributes&.key?(:all_records) ? attributes[:all_records] : false
|
50
|
+
input_ids = attributes&.key?(:ids) ? attributes[:ids] : params[:data].map { |item| item['id'] }
|
51
51
|
ids = unpack_ids(
|
52
52
|
collection,
|
53
|
-
are_excluded ? attributes[
|
53
|
+
are_excluded ? attributes[:all_records_ids_excluded] : input_ids,
|
54
54
|
with_key: with_key
|
55
55
|
)
|
56
56
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class ActionFields
|
5
|
+
def self.collection_field?(field)
|
6
|
+
field&.type == 'Collection'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.enum_field?(field)
|
10
|
+
field&.type == 'Enum'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.enum_list_field?(field)
|
14
|
+
field&.type == 'EnumList'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.file_field?(field)
|
18
|
+
field&.type == 'File'
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.file_list_field?(field)
|
22
|
+
field&.type == 'FileList'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class ForestValueConverter
|
5
|
+
# This last form data parser tries to guess the types from the data itself.
|
6
|
+
#
|
7
|
+
# - Fields with type "Collection" which target collections where the pk is not a string or
|
8
|
+
# derivative (mongoid, uuid, ...) won't be parser correctly, as we don't have enough information
|
9
|
+
# to properly guess the type
|
10
|
+
# - Fields of type "String" but where the final user entered a data-uri manually in the frontend
|
11
|
+
# will be wrongfully parsed.
|
12
|
+
def self.make_form_data_unsafe(raw_data)
|
13
|
+
data = {}
|
14
|
+
raw_data.each do |key, value|
|
15
|
+
# Skip fields from the default form
|
16
|
+
next if Schema::GeneratorAction::DEFAULT_FIELDS.map { |f| f[:field] }.include?(key)
|
17
|
+
|
18
|
+
data[key] = if value.is_a?(Array) && value.all? { |v| data_uri?(v) }
|
19
|
+
value.map { |uri| parse_data_uri(uri) }
|
20
|
+
elsif data_uri?(value)
|
21
|
+
parse_data_uri(value)
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
data
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.make_form_data_from_fields(datasource, fields)
|
31
|
+
data = {}
|
32
|
+
fields.each_value do |field|
|
33
|
+
next if Schema::GeneratorAction::DEFAULT_FIELDS.map { |f| f[:field] }.include?(field.field)
|
34
|
+
|
35
|
+
if field.reference && field.value
|
36
|
+
collection_name = field.reference.split('.').first
|
37
|
+
collection = datasource.get_collection(collection_name)
|
38
|
+
data[field.field] = Utils::Id.unpack_id(collection, field.value)
|
39
|
+
elsif field.type == 'File'
|
40
|
+
data[field.field] = parse_data_uri(field.value)
|
41
|
+
elsif field.type.is_a?(Array) && field.type[0] == 'File'
|
42
|
+
data[field.field] = field.value.map { |v| parse_data_uri(v) }
|
43
|
+
else
|
44
|
+
data[field.field] = field.value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
# Proper form data parser which converts data from an action form result to the format
|
52
|
+
# that is internally used in datasources.
|
53
|
+
def self.make_form_data(datasource, raw_data, fields)
|
54
|
+
data = {}
|
55
|
+
raw_data.each do |key, value|
|
56
|
+
field = fields.find { |f| f.label == key }
|
57
|
+
# Skip fields from the default form
|
58
|
+
next if Schema::GeneratorAction::DEFAULT_FIELDS.map { |f| f[:field] }.include?(key)
|
59
|
+
|
60
|
+
if ActionFields.collection_field?(field) && !value.nil?
|
61
|
+
collection = datasource.get_collection(field.collection_name)
|
62
|
+
data[key] = Utils::Id.unpack_id(collection, value)
|
63
|
+
elsif ActionFields.file_field?(field)
|
64
|
+
data[key] = parse_data_uri(value)
|
65
|
+
elsif ActionFields.file_list_field?(field)
|
66
|
+
data[key] = value.map { |v| parse_data_uri(v) }
|
67
|
+
else
|
68
|
+
data[key] = value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
data
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.data_uri?(value)
|
76
|
+
value.is_a?(String) && value.start_with?('data:')
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.value_to_forest(field)
|
80
|
+
if ActionFields.enum_field?(field)
|
81
|
+
return field.enum_values.include?(field.value) ? field.value : nil
|
82
|
+
end
|
83
|
+
|
84
|
+
return field.value.select { |v| field.enum_values.include?(v) } if ActionFields.enum_list_field?(field)
|
85
|
+
|
86
|
+
return field.value.join('|') if ActionFields.collection_field?(field)
|
87
|
+
|
88
|
+
# return make_data_uri(field.value) if ActionFields.file_field?(field)
|
89
|
+
#
|
90
|
+
# return value.map { |f| make_data_uri(f) } if ActionFields.file_list_field?(field)
|
91
|
+
|
92
|
+
field.value
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.make_data_uri(file)
|
96
|
+
# TODO: to implement
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.parse_data_uri(file)
|
100
|
+
# TODO: to implement
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class GeneratorAction
|
5
|
+
DEFAULT_FIELDS = [
|
6
|
+
{
|
7
|
+
field: 'Loading...',
|
8
|
+
type: 'String',
|
9
|
+
isReadOnly: true,
|
10
|
+
defaultValue: 'Form is loading',
|
11
|
+
value: nil,
|
12
|
+
description: '',
|
13
|
+
enums: nil,
|
14
|
+
hook: nil,
|
15
|
+
isRequired: false,
|
16
|
+
reference: nil,
|
17
|
+
widgetEdit: nil
|
18
|
+
}
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
def self.get_action_slug(name)
|
22
|
+
name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '')
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.build_schema(collection, name)
|
26
|
+
schema = collection.schema[:actions][name]
|
27
|
+
action_index = collection.schema[:actions].keys.index(name)
|
28
|
+
slug = get_action_slug(name)
|
29
|
+
fields = build_fields(collection, name, schema)
|
30
|
+
|
31
|
+
{
|
32
|
+
id: "#{collection.name}-#{action_index}-#{slug}",
|
33
|
+
name: name,
|
34
|
+
type: schema.scope.downcase,
|
35
|
+
baseUrl: nil,
|
36
|
+
endpoint: "/forest/_actions/#{collection.name}/#{action_index}/#{slug}",
|
37
|
+
httpMethod: 'POST',
|
38
|
+
redirect: nil, # frontend ignores this attribute
|
39
|
+
download: schema.is_generate_file,
|
40
|
+
fields: fields,
|
41
|
+
hooks: {
|
42
|
+
load: !schema.static_form?,
|
43
|
+
# Always registering the change hook has no consequences, even if we don't use it.
|
44
|
+
change: ['changeHook']
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.build_field_schema(datasource, field)
|
50
|
+
output = {
|
51
|
+
description: field.description,
|
52
|
+
isRequired: field.is_required,
|
53
|
+
isReadOnly: field.is_read_only,
|
54
|
+
field: field.label,
|
55
|
+
value: ForestValueConverter.value_to_forest(field)
|
56
|
+
}
|
57
|
+
|
58
|
+
output[:hook] = 'changeHook' if field.respond_to?(:watch_changes) && field.watch_changes
|
59
|
+
|
60
|
+
if ActionFields.collection_field?(field)
|
61
|
+
collection = datasource.get_collection(field.collection_name)
|
62
|
+
pk = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection)
|
63
|
+
pk_schema = collection.schema[:fields][pk]
|
64
|
+
|
65
|
+
output[:type] = pk_schema.column_type
|
66
|
+
output[:reference] = "#{collection.name}.#{pk}"
|
67
|
+
elsif field.type.end_with?('List')
|
68
|
+
output[:type] = [field.type.delete_suffix('List')]
|
69
|
+
else
|
70
|
+
output[:type] = field.type
|
71
|
+
end
|
72
|
+
|
73
|
+
output[:enums] = field.enum_values if ActionFields.enum_field?(field) || ActionFields.enum_list_field?(field)
|
74
|
+
|
75
|
+
output
|
76
|
+
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
private
|
80
|
+
|
81
|
+
def build_fields(collection, name, action)
|
82
|
+
return DEFAULT_FIELDS unless action.static_form?
|
83
|
+
|
84
|
+
fields = collection.get_form(nil, name)
|
85
|
+
if fields
|
86
|
+
return fields.map do |field|
|
87
|
+
new_field = build_field_schema(collection.datasource, field)
|
88
|
+
new_field[:default_value] = new_field[:value]
|
89
|
+
new_field.delete(:value)
|
90
|
+
|
91
|
+
new_field
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
[]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -6,7 +6,7 @@ module ForestAdminAgent
|
|
6
6
|
|
7
7
|
def self.build_schema(collection)
|
8
8
|
{
|
9
|
-
actions:
|
9
|
+
actions: build_actions(collection),
|
10
10
|
fields: build_fields(collection),
|
11
11
|
icon: nil,
|
12
12
|
integration: nil,
|
@@ -29,6 +29,14 @@ module ForestAdminAgent
|
|
29
29
|
fields.map { |name, _field| GeneratorField.build_schema(collection, name) }
|
30
30
|
.sort_by { |v| v[:field] }
|
31
31
|
end
|
32
|
+
|
33
|
+
def self.build_actions(collection)
|
34
|
+
if collection.schema[:actions]
|
35
|
+
collection.schema[:actions].keys.sort.map { |name| GeneratorAction.build_schema(collection, name) }
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -7,7 +7,7 @@ module ForestAdminAgent
|
|
7
7
|
class SchemaEmitter
|
8
8
|
LIANA_NAME = "forest-rails"
|
9
9
|
|
10
|
-
LIANA_VERSION = "1.0.0-beta.
|
10
|
+
LIANA_VERSION = "1.0.0-beta.30"
|
11
11
|
|
12
12
|
def self.get_serialized_schema(datasource)
|
13
13
|
schema_path = Facades::Container.cache(:schema_path)
|
@@ -75,7 +75,7 @@ module ForestAdminAgent
|
|
75
75
|
attributes: collection,
|
76
76
|
relationships: {
|
77
77
|
actions: { data: get_smart_features_by_collection('actions', collection_actions) },
|
78
|
-
segments: { data: get_smart_features_by_collection('segments',
|
78
|
+
segments: { data: get_smart_features_by_collection('segments', collection_segments) }
|
79
79
|
}
|
80
80
|
}
|
81
81
|
)
|
@@ -83,7 +83,7 @@ module ForestAdminAgent
|
|
83
83
|
|
84
84
|
{
|
85
85
|
data: data,
|
86
|
-
included: included.reject!(&:empty?),
|
86
|
+
included: included.reject!(&:empty?)&.flatten,
|
87
87
|
meta: schema[:meta]
|
88
88
|
}
|
89
89
|
end
|
@@ -95,6 +95,8 @@ module ForestAdminAgent
|
|
95
95
|
smart_feature[:attributes] = value if with_attributes
|
96
96
|
smart_features << smart_feature
|
97
97
|
end
|
98
|
+
|
99
|
+
smart_features
|
98
100
|
end
|
99
101
|
end
|
100
102
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_admin_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.beta.
|
4
|
+
version: 1.0.0.pre.beta.30
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthieu
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-02-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -242,6 +242,7 @@ files:
|
|
242
242
|
- lib/forest_admin_agent/routes/abstract_authenticated_route.rb
|
243
243
|
- lib/forest_admin_agent/routes/abstract_related_route.rb
|
244
244
|
- lib/forest_admin_agent/routes/abstract_route.rb
|
245
|
+
- lib/forest_admin_agent/routes/action/action.rb
|
245
246
|
- lib/forest_admin_agent/routes/charts/charts.rb
|
246
247
|
- lib/forest_admin_agent/routes/resources/count.rb
|
247
248
|
- lib/forest_admin_agent/routes/resources/delete.rb
|
@@ -270,7 +271,10 @@ files:
|
|
270
271
|
- lib/forest_admin_agent/utils/error_messages.rb
|
271
272
|
- lib/forest_admin_agent/utils/id.rb
|
272
273
|
- lib/forest_admin_agent/utils/query_string_parser.rb
|
274
|
+
- lib/forest_admin_agent/utils/schema/action_fields.rb
|
275
|
+
- lib/forest_admin_agent/utils/schema/forest_value_converter.rb
|
273
276
|
- lib/forest_admin_agent/utils/schema/frontend_filterable.rb
|
277
|
+
- lib/forest_admin_agent/utils/schema/generator_action.rb
|
274
278
|
- lib/forest_admin_agent/utils/schema/generator_collection.rb
|
275
279
|
- lib/forest_admin_agent/utils/schema/generator_field.rb
|
276
280
|
- lib/forest_admin_agent/utils/schema/schema_emitter.rb
|
@@ -288,7 +292,7 @@ files:
|
|
288
292
|
- sig/forest_admin_agent/routes/system/health_check.rbs
|
289
293
|
homepage: https://www.forestadmin.com
|
290
294
|
licenses:
|
291
|
-
-
|
295
|
+
- GPL-3.0
|
292
296
|
metadata:
|
293
297
|
homepage_uri: https://www.forestadmin.com
|
294
298
|
source_code_uri: https://github.com/ForestAdmin/agent-ruby
|