forest_admin_agent 1.0.0.pre.beta.48 → 1.0.0.pre.beta.50
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/forest_admin_agent/builder/agent_factory.rb +10 -0
- data/lib/forest_admin_agent/facades/container.rb +3 -1
- data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +2 -2
- data/lib/forest_admin_agent/http/error_handling.rb +18 -0
- data/lib/forest_admin_agent/routes/action/action.rb +1 -1
- data/lib/forest_admin_agent/routes/charts/api_chart_datasource.rb +9 -5
- data/lib/forest_admin_agent/routes/resources/list.rb +3 -1
- data/lib/forest_admin_agent/routes/resources/related/count_related.rb +1 -1
- data/lib/forest_admin_agent/serializer/forest_serializer.rb +3 -3
- data/lib/forest_admin_agent/services/logger_service.rb +1 -2
- data/lib/forest_admin_agent/services/permissions.rb +3 -2
- data/lib/forest_admin_agent/services/sse_cache_invalidation.rb +19 -7
- data/lib/forest_admin_agent/utils/id.rb +1 -1
- data/lib/forest_admin_agent/utils/query_string_parser.rb +15 -4
- data/lib/forest_admin_agent/utils/schema/forest_value_converter.rb +12 -11
- data/lib/forest_admin_agent/utils/schema/frontend_validation_utils.rb +127 -0
- data/lib/forest_admin_agent/utils/schema/generator_action.rb +2 -1
- data/lib/forest_admin_agent/utils/schema/generator_collection.rb +9 -1
- data/lib/forest_admin_agent/utils/schema/generator_field.rb +11 -11
- data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +1 -1
- data/lib/forest_admin_agent/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfbe3f9efe01b5e48063269ededcdb2a57348879de0d7137e8dc5b45687be74a
|
4
|
+
data.tar.gz: 311351c004cd6d42e8ddb3edb69921cd4a47ab3b9461dd9cd7b03be5b73fcbf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2722461028fa38f8768c3f5f485378eb64b02011a1c9dc77d28939b259e9e29446573a331d50b42796a6f1db216c1792bb51bb65ce3aabbc0d767635e642640
|
7
|
+
data.tar.gz: 9ceed842c48e472ecfa71bc53b296087974b246edd16038b14358204c61f4dd2281a414944e63fd51f04cf783a6026533d6c0f33f636eba4a58f680b8b15fac0
|
@@ -34,6 +34,8 @@ module ForestAdminAgent
|
|
34
34
|
|
35
35
|
def customize_collection(name, &handle)
|
36
36
|
@customizer.customize_collection(name, handle)
|
37
|
+
|
38
|
+
self
|
37
39
|
end
|
38
40
|
|
39
41
|
def build
|
@@ -74,6 +76,14 @@ module ForestAdminAgent
|
|
74
76
|
return unless @has_env_secret
|
75
77
|
|
76
78
|
cache = @container.resolve(:cache)
|
79
|
+
@options[:customize_error_message] = @options[:customize_error_message]
|
80
|
+
&.source
|
81
|
+
&.strip
|
82
|
+
&.delete_prefix('config.customize_error_message =')
|
83
|
+
&.strip
|
84
|
+
|
85
|
+
@options[:logger] = @options[:logger]&.source&.strip&.delete_prefix('config.logger =')&.strip
|
86
|
+
|
77
87
|
cache.set('config', @options.to_h)
|
78
88
|
end
|
79
89
|
|
@@ -2,12 +2,12 @@ module ForestAdminAgent
|
|
2
2
|
module Http
|
3
3
|
module Exceptions
|
4
4
|
class AuthenticationOpenIdClient < HttpException
|
5
|
-
attr_reader :error, :
|
5
|
+
attr_reader :error, :message, :state
|
6
6
|
|
7
7
|
def initialize(error, error_description, state)
|
8
8
|
super(error, 401, error_description)
|
9
9
|
@error = error
|
10
|
-
@
|
10
|
+
@message = error_description
|
11
11
|
@state = state
|
12
12
|
end
|
13
13
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Http
|
3
|
+
module ErrorHandling
|
4
|
+
def get_error_message(error)
|
5
|
+
if error.respond_to?(:ancestors) && error.ancestors.include?(ForestAdminAgent::Http::Exceptions::HttpException)
|
6
|
+
return error.message
|
7
|
+
end
|
8
|
+
|
9
|
+
if (customizer = ForestAdminAgent::Facades::Container.cache(:customize_error_message))
|
10
|
+
message = eval(customizer).call(error)
|
11
|
+
return message if message
|
12
|
+
end
|
13
|
+
|
14
|
+
'Unexpected error'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -20,24 +20,26 @@ module ForestAdminAgent
|
|
20
20
|
"forest_chart_get_#{slug}",
|
21
21
|
'get',
|
22
22
|
"/_charts/#{slug}",
|
23
|
-
proc { handle_smart_chart }
|
23
|
+
proc { |args| handle_smart_chart(args) }
|
24
24
|
)
|
25
25
|
|
26
26
|
add_route(
|
27
27
|
"forest_chart_post_#{slug}",
|
28
28
|
'post',
|
29
29
|
"/_charts/#{slug}",
|
30
|
-
proc { handle_api_chart }
|
30
|
+
proc { |args| handle_api_chart(args) }
|
31
31
|
)
|
32
32
|
|
33
33
|
unless Facades::Container.cache(:is_production)
|
34
|
-
Facades::Container.logger.log('Info', "/forest/_charts/#{slug}")
|
34
|
+
Facades::Container.logger.log('Info', "Chart #{@chart_name} was mounted at /forest/_charts/#{slug}")
|
35
35
|
end
|
36
36
|
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
|
-
def handle_api_chart
|
40
|
+
def handle_api_chart(args = {})
|
41
|
+
@caller = Utils::QueryStringParser.parse_caller(args)
|
42
|
+
|
41
43
|
{
|
42
44
|
content: Serializer::ForestChartSerializer.serialize(
|
43
45
|
@datasource.render_chart(
|
@@ -48,7 +50,9 @@ module ForestAdminAgent
|
|
48
50
|
}
|
49
51
|
end
|
50
52
|
|
51
|
-
def handle_smart_chart
|
53
|
+
def handle_smart_chart(args = {})
|
54
|
+
@caller = Utils::QueryStringParser.parse_caller(args)
|
55
|
+
|
52
56
|
{
|
53
57
|
content: @datasource.render_chart(
|
54
58
|
@caller,
|
@@ -23,7 +23,9 @@ module ForestAdminAgent
|
|
23
23
|
]),
|
24
24
|
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args),
|
25
25
|
search: ForestAdminAgent::Utils::QueryStringParser.parse_search(@collection, args),
|
26
|
-
search_extended: ForestAdminAgent::Utils::QueryStringParser.parse_search_extended(args)
|
26
|
+
search_extended: ForestAdminAgent::Utils::QueryStringParser.parse_search_extended(args),
|
27
|
+
sort: ForestAdminAgent::Utils::QueryStringParser.parse_sort(@collection, args),
|
28
|
+
segment: ForestAdminAgent::Utils::QueryStringParser.parse_segment(@collection, args)
|
27
29
|
)
|
28
30
|
|
29
31
|
projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@collection, args)
|
@@ -16,7 +16,7 @@ module ForestAdminAgent
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def base_url
|
19
|
-
|
19
|
+
'/forest'
|
20
20
|
end
|
21
21
|
|
22
22
|
def type
|
@@ -177,11 +177,11 @@ module ForestAdminAgent
|
|
177
177
|
end
|
178
178
|
|
179
179
|
def relationship_self_link(attribute_name)
|
180
|
-
"
|
180
|
+
"#{self_link}/relationships/#{format_name(attribute_name)}"
|
181
181
|
end
|
182
182
|
|
183
183
|
def relationship_related_link(attribute_name)
|
184
|
-
"
|
184
|
+
"#{self_link}/#{format_name(attribute_name)}"
|
185
185
|
end
|
186
186
|
end
|
187
187
|
end
|
@@ -16,12 +16,11 @@ module ForestAdminAgent
|
|
16
16
|
@logger_level = logger_level
|
17
17
|
@logger = logger
|
18
18
|
@default_logger = MonoLogger.new($stdout)
|
19
|
-
# TODO: HANDLE FORMATTER
|
20
19
|
end
|
21
20
|
|
22
21
|
def log(level, message)
|
23
22
|
if @logger
|
24
|
-
@logger.call(get_level(level), message)
|
23
|
+
eval(@logger).call(get_level(level), message)
|
25
24
|
else
|
26
25
|
@default_logger.add(get_level(level), message)
|
27
26
|
end
|
@@ -95,7 +95,7 @@ module ForestAdminAgent
|
|
95
95
|
filter
|
96
96
|
)
|
97
97
|
|
98
|
-
smart_action_approval.can_execute?
|
98
|
+
is_allowed = smart_action_approval.can_execute?
|
99
99
|
ForestAdminAgent::Facades::Container.logger.log(
|
100
100
|
'Debug',
|
101
101
|
"User #{user_data[:roleId]} is #{is_allowed ? "" : "not"} allowed to perform #{action["name"]}"
|
@@ -213,7 +213,8 @@ module ForestAdminAgent
|
|
213
213
|
end[:enable]
|
214
214
|
end
|
215
215
|
|
216
|
-
def find_action_from_endpoint(collection_name,
|
216
|
+
def find_action_from_endpoint(collection_name, path, http_method)
|
217
|
+
endpoint = path.partition('/forest/')[1..].join
|
217
218
|
schema_file = JSON.parse(File.read(Facades::Container.config_from_cache[:schema_path]))
|
218
219
|
actions = schema_file['collections']&.select { |collection| collection['name'] == collection_name }&.first&.dig('actions')
|
219
220
|
|
@@ -26,18 +26,30 @@ module ForestAdminAgent
|
|
26
26
|
|
27
27
|
MESSAGE_CACHE_KEYS[event.type]&.each do |cache_key|
|
28
28
|
Permissions.invalidate_cache(cache_key)
|
29
|
-
|
30
|
-
|
29
|
+
ForestAdminAgent::Facades::Container.logger.log(
|
30
|
+
'Info',
|
31
|
+
"invalidate cache #{MESSAGE_CACHE_KEYS[event.type]} for event #{event.type}"
|
32
|
+
)
|
31
33
|
end
|
32
|
-
|
33
|
-
|
34
|
+
|
35
|
+
ForestAdminAgent::Facades::Container.logger.log(
|
36
|
+
'Info',
|
37
|
+
"SSECacheInvalidation: unhandled message from server: #{event.type}"
|
38
|
+
)
|
34
39
|
end
|
35
40
|
end
|
36
41
|
rescue StandardError
|
42
|
+
ForestAdminAgent::Facades::Container.logger.log(
|
43
|
+
'Debug',
|
44
|
+
'SSE connection to forestadmin server'
|
45
|
+
)
|
46
|
+
|
47
|
+
ForestAdminAgent::Facades::Container.logger.log(
|
48
|
+
'Warning',
|
49
|
+
'SSE connection to forestadmin server closed unexpectedly, retrying.'
|
50
|
+
)
|
51
|
+
|
37
52
|
raise ForestException, 'Failed to reach SSE data from ForestAdmin server.'
|
38
|
-
# TODO: HANDLE LOGGER
|
39
|
-
# "debug", "SSE connection to forestadmin server due to ..."
|
40
|
-
# "warning", "SSE connection to forestadmin server closed unexpectedly, retrying."
|
41
53
|
end
|
42
54
|
end
|
43
55
|
end
|
@@ -27,7 +27,7 @@ module ForestAdminAgent
|
|
27
27
|
field = collection.schema[:fields][pk_name]
|
28
28
|
value = primary_key_values[index]
|
29
29
|
casted_value = field.column_type == 'Number' ? value.to_i : value
|
30
|
-
|
30
|
+
ForestAdminDatasourceToolkit::Validations::FieldValidator.validate_value(value, field, casted_value)
|
31
31
|
|
32
32
|
[pk_name, casted_value]
|
33
33
|
end.to_h
|
@@ -23,16 +23,16 @@ module ForestAdminAgent
|
|
23
23
|
return if filters.nil?
|
24
24
|
|
25
25
|
filters = JSON.parse(filters, symbolize_names: true) if filters.is_a? String
|
26
|
-
# TODO: add else for convert all keys to sym
|
27
26
|
|
28
27
|
ConditionTreeParser.from_plain_object(collection, filters)
|
29
|
-
# TODO: ConditionTreeValidator::validate($conditionTree, $collection);
|
30
28
|
end
|
31
29
|
|
32
30
|
def self.parse_caller(args)
|
33
31
|
unless args.dig(:headers, 'HTTP_AUTHORIZATION')
|
34
|
-
|
35
|
-
|
32
|
+
raise Http::Exceptions::HttpException.new(
|
33
|
+
401,
|
34
|
+
'You must be logged in to access at this resource.'
|
35
|
+
)
|
36
36
|
end
|
37
37
|
|
38
38
|
timezone = args[:params]['timezone']
|
@@ -125,6 +125,17 @@ module ForestAdminAgent
|
|
125
125
|
|
126
126
|
sort
|
127
127
|
end
|
128
|
+
|
129
|
+
def self.parse_segment(collection, args)
|
130
|
+
segment = args.dig(:params, :data, :attributes, :all_records_subset_query,
|
131
|
+
:segment) || args.dig(:params, :segment)
|
132
|
+
|
133
|
+
return unless segment
|
134
|
+
|
135
|
+
raise ForestException, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment)
|
136
|
+
|
137
|
+
segment
|
138
|
+
end
|
128
139
|
end
|
129
140
|
end
|
130
141
|
end
|
@@ -29,19 +29,20 @@ module ForestAdminAgent
|
|
29
29
|
|
30
30
|
def self.make_form_data_from_fields(datasource, fields)
|
31
31
|
data = {}
|
32
|
-
fields.each_value do |field|
|
33
|
-
next if Schema::GeneratorAction::DEFAULT_FIELDS.map { |f| f[:field] }.include?(field.field)
|
34
32
|
|
35
|
-
|
36
|
-
|
33
|
+
fields.each do |field|
|
34
|
+
next if Schema::GeneratorAction::DEFAULT_FIELDS.map { |f| f[:field] }.include?(field['field'])
|
35
|
+
|
36
|
+
if field['reference'] && field['value']
|
37
|
+
collection_name = field['reference'].split('.').first
|
37
38
|
collection = datasource.get_collection(collection_name)
|
38
|
-
data[field
|
39
|
-
elsif field
|
40
|
-
data[field
|
41
|
-
elsif field
|
42
|
-
data[field
|
39
|
+
data[field['field']] = Utils::Id.unpack_id(collection, field['value'])
|
40
|
+
elsif field['type'] == 'File'
|
41
|
+
data[field['field']] = parse_data_uri(field['value'])
|
42
|
+
elsif field['type'].is_a?(Array) && field['type'][0] == 'File'
|
43
|
+
data[field['field']] = field['value'].map { |v| parse_data_uri(v) }
|
43
44
|
else
|
44
|
-
data[field
|
45
|
+
data[field['field']] = field['value']
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
@@ -83,7 +84,7 @@ module ForestAdminAgent
|
|
83
84
|
|
84
85
|
return field.value.select { |v| field.enum_values.include?(v) } if ActionFields.enum_list_field?(field)
|
85
86
|
|
86
|
-
return field.value
|
87
|
+
return field.value&.join('|') if ActionFields.collection_field?(field)
|
87
88
|
|
88
89
|
# return make_data_uri(field.value) if ActionFields.file_field?(field)
|
89
90
|
#
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class FrontendValidationUtils
|
5
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
6
|
+
|
7
|
+
# Those operators depend on the current time so they won't work.
|
8
|
+
# The reason is that we need now() to be evaluated at query time, not at schema generation time.
|
9
|
+
EXCLUDED = [Operators::FUTURE, Operators::PAST, Operators::TODAY, Operators::YESTERDAY,
|
10
|
+
Operators::PREVIOUS_MONTH, Operators::PREVIOUS_QUARTER, Operators::PREVIOUS_WEEK,
|
11
|
+
Operators::PREVIOUS_X_DAYS, Operators::PREVIOUS_YEAR, Operators::AFTER_X_HOURS_AGO,
|
12
|
+
Operators::BEFORE_X_HOURS_AGO, Operators::PREVIOUS_X_DAYS_TO_DATE,
|
13
|
+
Operators::PREVIOUS_MONTH_TO_DATE, Operators::PREVIOUS_QUARTER_TO_DATE,
|
14
|
+
Operators::PREVIOUS_WEEK_TO_DATE, Operators::PREVIOUS_YEAR_TO_DATE].freeze
|
15
|
+
|
16
|
+
SUPPORTED = {
|
17
|
+
Operators::PRESENT => proc { { type: 'is present', message: 'Field is required' } },
|
18
|
+
Operators::AFTER => proc do |rule|
|
19
|
+
{ type: 'is after', value: rule[:value], message: "Value must be after #{rule[:value]}" }
|
20
|
+
end,
|
21
|
+
Operators::BEFORE => proc do |rule|
|
22
|
+
{ type: 'is before', value: rule[:value], message: "Value must be before #{rule[:value]}" }
|
23
|
+
end,
|
24
|
+
Operators::CONTAINS => proc do |rule|
|
25
|
+
{ type: 'is contains', value: rule[:value], message: "Value must contain #{rule[:value]}" }
|
26
|
+
end,
|
27
|
+
Operators::GREATER_THAN => proc do |rule|
|
28
|
+
{ type: 'is greater than', value: rule[:value], message: "Value must be greater than #{rule[:value]}" }
|
29
|
+
end,
|
30
|
+
Operators::LESS_THAN => proc do |rule|
|
31
|
+
{ type: 'is less than', value: rule[:value], message: "Value must be lower than #{rule[:value]}" }
|
32
|
+
end,
|
33
|
+
Operators::LONGER_THAN => proc do |rule|
|
34
|
+
{ type: 'is longer than', value: rule[:value],
|
35
|
+
message: "Value must be longer than #{rule[:value]} characters" }
|
36
|
+
end,
|
37
|
+
Operators::SHORTER_THAN => proc do |rule|
|
38
|
+
{
|
39
|
+
type: 'is shorter than',
|
40
|
+
value: rule[:value],
|
41
|
+
message: "Value must be shorter than #{rule[:value]} characters"
|
42
|
+
}
|
43
|
+
end,
|
44
|
+
Operators::MATCH => proc do |rule|
|
45
|
+
{
|
46
|
+
type: 'is like', # `is like` actually expects a regular expression, not a 'like pattern'
|
47
|
+
value: rule[:value].to_s,
|
48
|
+
message: "Value must match #{rule[:value]}"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
def self.convert_validation_list(column)
|
54
|
+
return [] if column.validations.empty?
|
55
|
+
|
56
|
+
rules = column.validations.dup.map { |rule| simplify_rule(column.column_type, rule) }
|
57
|
+
remove_duplicates_in_place(rules).map { |rule| SUPPORTED[rule[:operator]].call(rule) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.simplify_rule(column_type, rule)
|
61
|
+
return [] if EXCLUDED.include?(rule[:operator])
|
62
|
+
|
63
|
+
return rule if SUPPORTED.key?(rule[:operator])
|
64
|
+
|
65
|
+
begin
|
66
|
+
# Add the 'Equal|NotEqual' operators to unlock the `In|NotIn -> Match` replacement rules.
|
67
|
+
# This is a bit hacky, but it allows to reuse the existing logic.
|
68
|
+
operators = SUPPORTED.keys
|
69
|
+
operators << Operators::EQUAL
|
70
|
+
operators << Operators::NOT_EQUAL
|
71
|
+
|
72
|
+
# Rewrite the rule to use only operators that the frontend supports.
|
73
|
+
leaf = Nodes::ConditionTreeLeaf.new('field', rule[:operator], rule[:value])
|
74
|
+
timezone = 'Europe/Paris' # we're sending the schema => use random tz
|
75
|
+
tree = ConditionTreeEquivalent.get_equivalent_tree(leaf, operators, column_type, timezone)
|
76
|
+
|
77
|
+
if tree.is_a? Nodes::ConditionTreeLeaf
|
78
|
+
[tree]
|
79
|
+
else
|
80
|
+
tree.conditions
|
81
|
+
end
|
82
|
+
rescue StandardError
|
83
|
+
# Just ignore errors, they mean that the operator is not supported by the frontend
|
84
|
+
# and that we don't have an automatic conversion for it.
|
85
|
+
#
|
86
|
+
# In that case we fallback to just validating the data entry in the agent (which is better
|
87
|
+
# than nothing but will not be as user friendly as the frontend validation).
|
88
|
+
end
|
89
|
+
|
90
|
+
# Drop the rule if we don't know how to convert it (we could log a warning here).
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
|
94
|
+
# The frontend crashes when it receives multiple rules of the same type.
|
95
|
+
# This method merges the rules which can be merged and drops the others.
|
96
|
+
def self.remove_duplicates_in_place(rules)
|
97
|
+
used = {}
|
98
|
+
rules.each_with_index do |rule, key|
|
99
|
+
if used.key?(rule[:operator])
|
100
|
+
rule = rules[rule[:operator]]
|
101
|
+
new_rule = rule
|
102
|
+
rules.delete(key)
|
103
|
+
rules[used[rule[:operator]]] = merge_into(rule, new_rule)
|
104
|
+
else
|
105
|
+
used[rule[:operator]] = key
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
rules
|
110
|
+
end
|
111
|
+
|
112
|
+
def merge_into(rule, new_rule)
|
113
|
+
if [Operators::GREATER_THAN, Operators::AFTER, Operators::LONGER_THAN].include? rule[:operator]
|
114
|
+
rule[:value] = [rule[:value], new_rule[:value]].max
|
115
|
+
elsif [Operators::LESS_THAN, Operators::BEFORE, Operators::SHORTER_THAN].include? rule[:operator]
|
116
|
+
rule[:value] = [rule[:value], new_rule[:value]].min
|
117
|
+
elsif rule[:operator] == Operators::MATCH
|
118
|
+
# TODO
|
119
|
+
end
|
120
|
+
# else Ignore the rules that we can't deduplicate (we could log a warning here).
|
121
|
+
|
122
|
+
rule
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -59,7 +59,7 @@ module ForestAdminAgent
|
|
59
59
|
|
60
60
|
if ActionFields.collection_field?(field)
|
61
61
|
collection = datasource.get_collection(field.collection_name)
|
62
|
-
pk = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection)
|
62
|
+
pk = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection)[0]
|
63
63
|
pk_schema = collection.schema[:fields][pk]
|
64
64
|
|
65
65
|
output[:type] = pk_schema.column_type
|
@@ -82,6 +82,7 @@ module ForestAdminAgent
|
|
82
82
|
return DEFAULT_FIELDS unless action.static_form?
|
83
83
|
|
84
84
|
fields = collection.get_form(nil, name)
|
85
|
+
|
85
86
|
if fields
|
86
87
|
return fields.map do |field|
|
87
88
|
new_field = build_field_schema(collection.datasource, field)
|
@@ -16,7 +16,7 @@ module ForestAdminAgent
|
|
16
16
|
name: collection.name,
|
17
17
|
onlyForRelationships: false,
|
18
18
|
paginationType: 'page',
|
19
|
-
segments:
|
19
|
+
segments: build_segments(collection)
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
@@ -37,6 +37,14 @@ module ForestAdminAgent
|
|
37
37
|
{}
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
def self.build_segments(collection)
|
42
|
+
if collection.schema[:segments]
|
43
|
+
collection.schema[:segments].keys.sort.map { |name| { id: "#{collection.name}.#{name}", name: name } }
|
44
|
+
else
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
end
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
@@ -35,8 +35,7 @@ module ForestAdminAgent
|
|
35
35
|
field: name,
|
36
36
|
integration: nil,
|
37
37
|
inverseOf: nil,
|
38
|
-
|
39
|
-
isFilterable: true, # TODO: remove when implementing operators decorators
|
38
|
+
isFilterable: FrontendFilterable.filterable?(column.column_type, column.filter_operators),
|
40
39
|
isPrimaryKey: column.is_primary_key,
|
41
40
|
|
42
41
|
# When a column is a foreign key, it is readonly.
|
@@ -48,7 +47,7 @@ module ForestAdminAgent
|
|
48
47
|
isVirtual: false,
|
49
48
|
reference: nil,
|
50
49
|
type: convert_column_type(column.column_type),
|
51
|
-
validations:
|
50
|
+
validations: FrontendValidationUtils.convert_validation_list(column)
|
52
51
|
}
|
53
52
|
end
|
54
53
|
|
@@ -67,9 +66,10 @@ module ForestAdminAgent
|
|
67
66
|
}
|
68
67
|
end
|
69
68
|
|
70
|
-
def foreign_collection_filterable?
|
71
|
-
|
72
|
-
|
69
|
+
def foreign_collection_filterable?(foreign_collection)
|
70
|
+
foreign_collection.schema[:fields].values.any? do |field|
|
71
|
+
field.type == 'Column' && FrontendFilterable.filterable?(field.column_type, field.filter_operators)
|
72
|
+
end
|
73
73
|
end
|
74
74
|
|
75
75
|
def build_many_to_many_schema(relation, collection, foreign_collection, base_schema)
|
@@ -122,7 +122,7 @@ module ForestAdminAgent
|
|
122
122
|
{
|
123
123
|
type: key_field.column_type,
|
124
124
|
defaultValue: nil,
|
125
|
-
isFilterable: foreign_collection_filterable
|
125
|
+
isFilterable: foreign_collection_filterable?(foreign_collection),
|
126
126
|
isPrimaryKey: false,
|
127
127
|
isRequired: false,
|
128
128
|
isReadOnly: key_field.is_read_only,
|
@@ -140,12 +140,12 @@ module ForestAdminAgent
|
|
140
140
|
{
|
141
141
|
type: key_field.column_type,
|
142
142
|
defaultValue: key_field.default_value,
|
143
|
-
isFilterable: foreign_collection_filterable
|
143
|
+
isFilterable: foreign_collection_filterable?(foreign_collection),
|
144
144
|
isPrimaryKey: false,
|
145
|
-
isRequired:
|
145
|
+
isRequired: key_field.validations.any? { |v| v[:operator] == 'Present' },
|
146
146
|
isReadOnly: key_field.is_read_only,
|
147
147
|
isSortable: key_field.is_sortable,
|
148
|
-
validations:
|
148
|
+
validations: FrontendValidationUtils.convert_validation_list(key_field),
|
149
149
|
reference: "#{foreign_collection.name}.#{relation.foreign_key_target}"
|
150
150
|
}
|
151
151
|
)
|
@@ -161,7 +161,7 @@ module ForestAdminAgent
|
|
161
161
|
integration: nil,
|
162
162
|
isReadOnly: nil,
|
163
163
|
isVirtual: false,
|
164
|
-
inverseOf:
|
164
|
+
inverseOf: ForestAdminDatasourceToolkit::Utils::Collection.get_inverse_relation(collection, name),
|
165
165
|
relationship: RELATION_MAP[relation.type]
|
166
166
|
}
|
167
167
|
|
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.50
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthieu
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/forest_admin_agent/http/Exceptions/require_approval.rb
|
239
239
|
- lib/forest_admin_agent/http/Exceptions/unprocessable_error.rb
|
240
240
|
- lib/forest_admin_agent/http/Exceptions/validation_error.rb
|
241
|
+
- lib/forest_admin_agent/http/error_handling.rb
|
241
242
|
- lib/forest_admin_agent/http/forest_admin_api_requester.rb
|
242
243
|
- lib/forest_admin_agent/http/router.rb
|
243
244
|
- lib/forest_admin_agent/routes/abstract_authenticated_route.rb
|
@@ -280,6 +281,7 @@ files:
|
|
280
281
|
- lib/forest_admin_agent/utils/schema/action_fields.rb
|
281
282
|
- lib/forest_admin_agent/utils/schema/forest_value_converter.rb
|
282
283
|
- lib/forest_admin_agent/utils/schema/frontend_filterable.rb
|
284
|
+
- lib/forest_admin_agent/utils/schema/frontend_validation_utils.rb
|
283
285
|
- lib/forest_admin_agent/utils/schema/generator_action.rb
|
284
286
|
- lib/forest_admin_agent/utils/schema/generator_collection.rb
|
285
287
|
- lib/forest_admin_agent/utils/schema/generator_field.rb
|