forest_admin_agent 1.0.0.pre.beta.48 → 1.0.0.pre.beta.49

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40e4df2d99083f936e9cf9a4693f20d69cc6d3b2b86d8ef8c66abafdbd554d2b
4
- data.tar.gz: 732d5c6d7c20f67558d609c1a8e28d834bbb1b01bd0880988721409e4bc564d7
3
+ metadata.gz: 23ea6e5c071b956705a5f6fb880be2a837c07714c33117e20304ad07921ab7bf
4
+ data.tar.gz: 7637f501d7dcf319eedace4183e2b40675c71bd8faa5fc06e62a5a72f91c3580
5
5
  SHA512:
6
- metadata.gz: '009c1d66c244400c79b66dc66fa1acd0bcdd80de1c7cea811050352bdc1dc6ec494025a732f2a52b9368319db782da1fb720aa0cef1bc2d427c98a145da5614d'
7
- data.tar.gz: a349fd075afbc6ed33fd44aba00dcd71f79b2d8ec767b4877f7756c41540368f25827a2501b825b92af08e7db9ce426a8009972a0af44f5a2334acc75fc5ac29
6
+ metadata.gz: 0656cfcadb4df52020d45f816f49332337686c77ec5d1996448aa67f246be319aa8d1a43860ce63c558be15106751c249aff61fb2f9d27303c934b99bbe4d4a1
7
+ data.tar.gz: b9ac0d65d0dc13e3a5c011b22ccef0e5208da775087d3b95fbcc17ec7cc4427d78b5ad4e50c4d7b43b10e24750dc610928806dcde69abacf43e7a41f230411f7
@@ -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
 
@@ -6,7 +6,9 @@ module ForestAdminAgent
6
6
  end
7
7
 
8
8
  def self.datasource
9
- instance.resolve(:datasource)
9
+ instance.resolve(:datasource) do
10
+ ForestAdminDatasourceToolkit::Datasource.new
11
+ end
10
12
  end
11
13
 
12
14
  def self.logger
@@ -2,12 +2,12 @@ module ForestAdminAgent
2
2
  module Http
3
3
  module Exceptions
4
4
  class AuthenticationOpenIdClient < HttpException
5
- attr_reader :error, :error_description, :state
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
- @error_description = error_description
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
@@ -80,7 +80,7 @@ module ForestAdminAgent
80
80
  data,
81
81
  filter,
82
82
  {
83
- change_field: nil,
83
+ change_field: args.dig(:params, :data, :attributes, :changed_field),
84
84
  search_field: nil,
85
85
  search_values: {},
86
86
  includeHiddenFields: false
@@ -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)
@@ -38,7 +38,7 @@ module ForestAdminAgent
38
38
  return {
39
39
  name: @child_collection.name,
40
40
  content: {
41
- count: result[0][:value]
41
+ count: result.empty? ? 0 : result[0]['value']
42
42
  }
43
43
  }
44
44
  end
@@ -16,7 +16,7 @@ module ForestAdminAgent
16
16
  end
17
17
 
18
18
  def base_url
19
- Facades::Container.cache(:prefix)
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
- "/#{self_link}/relationships/#{format_name(attribute_name)}"
180
+ "#{self_link}/relationships/#{format_name(attribute_name)}"
181
181
  end
182
182
 
183
183
  def relationship_related_link(attribute_name)
184
- "/#{self_link}/#{format_name(attribute_name)}"
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, endpoint, http_method)
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
- # TODO: HANDLE LOGGER
30
- # "info","invalidate cache {MESSAGE_CACHE_KEYS[event.type]} for event {event.type}"
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
- # TODO: HANDLE LOGGER add else
33
- # "info", "SSECacheInvalidation: unhandled message from server: {event.type}"
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
- # TODO: call FieldValidator::validateValue($value, $field, $castedValue);
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
- # TODO: replace by http exception
35
- raise ForestException, 'You must be logged in to access at this resource.'
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
- if field.reference && field.value
36
- collection_name = field.reference.split('.').first
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.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) }
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.field] = field.value
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.join('|') if ActionFields.collection_field?(field)
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
- # isFilterable: FrontendFilterable.filterable?(column.column_type, column.filter_operators),
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: [] # TODO: FrontendValidationUtils.convertValidationList(column),
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
- # TODO: implement FrontendFilterable before
72
- true
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: false, # TODO: check with validations
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: [], # TODO: FrontendValidation::convertValidationList(foreignTargetColumn)
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: nil, # TODO: CollectionUtils::getInverseRelation(collection, name)
164
+ inverseOf: ForestAdminDatasourceToolkit::Utils::Collection.get_inverse_relation(collection, name),
165
165
  relationship: RELATION_MAP[relation.type]
166
166
  }
167
167
 
@@ -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.48"
10
+ LIANA_VERSION = "1.0.0-beta.49"
11
11
 
12
12
  def self.get_serialized_schema(datasource)
13
13
  schema_path = Facades::Container.cache(:schema_path)
@@ -1,3 +1,3 @@
1
1
  module ForestAdminAgent
2
- VERSION = "1.0.0-beta.48"
2
+ VERSION = "1.0.0-beta.49"
3
3
  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.48
4
+ version: 1.0.0.pre.beta.49
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