forest_admin_agent 1.0.0.pre.beta.48 → 1.0.0.pre.beta.50
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/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
|