forest_admin_datasource_customizer 1.0.0.pre.beta.21 → 1.0.0.pre.beta.57

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/forest_admin_datasource_customizer.gemspec +3 -2
  3. data/lib/forest_admin_datasource_customizer/collection_customizer.rb +292 -5
  4. data/lib/forest_admin_datasource_customizer/context/agent_customization_context.rb +18 -0
  5. data/lib/forest_admin_datasource_customizer/context/collection_customization_context.rb +15 -0
  6. data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_collection.rb +50 -0
  7. data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_data_source.rb +18 -0
  8. data/lib/forest_admin_datasource_customizer/datasource_customizer.rb +43 -13
  9. data/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +137 -0
  10. data/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +67 -0
  11. data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context.rb +56 -0
  12. data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context_single.rb +26 -0
  13. data/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb +50 -0
  14. data/lib/forest_admin_datasource_customizer/decorators/action/result_builder.rb +68 -0
  15. data/lib/forest_admin_datasource_customizer/decorators/action/types/action_scope.rb +15 -0
  16. data/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb +35 -0
  17. data/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb +357 -0
  18. data/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb +215 -0
  19. data/lib/forest_admin_datasource_customizer/decorators/binary/binary_helper.rb +17 -0
  20. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb +41 -0
  21. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb +33 -0
  22. data/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb +46 -0
  23. data/lib/forest_admin_datasource_customizer/decorators/chart/result_builder.rb +148 -0
  24. data/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb +115 -0
  25. data/lib/forest_admin_datasource_customizer/decorators/computed/computed_definition.rb +21 -0
  26. data/lib/forest_admin_datasource_customizer/decorators/computed/utils/computed_field.rb +74 -0
  27. data/lib/forest_admin_datasource_customizer/decorators/computed/utils/flattener.rb +49 -0
  28. data/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb +33 -4
  29. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_aggregate_context.rb +18 -0
  30. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_create_context.rb +18 -0
  31. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_delete_context.rb +12 -0
  32. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_list_context.rb +18 -0
  33. data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_update_context.rb +12 -0
  34. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_aggregate_context.rb +20 -0
  35. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_create_context.rb +18 -0
  36. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_delete_context.rb +18 -0
  37. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_list_context.rb +19 -0
  38. data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_update_context.rb +19 -0
  39. data/lib/forest_admin_datasource_customizer/decorators/hook/context/hook_context.rb +22 -0
  40. data/lib/forest_admin_datasource_customizer/decorators/hook/hook_collection_decorator.rb +95 -0
  41. data/lib/forest_admin_datasource_customizer/decorators/hook/hooks.rb +26 -0
  42. data/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb +118 -0
  43. data/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb +50 -0
  44. data/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb +16 -0
  45. data/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb +16 -0
  46. data/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb +17 -0
  47. data/lib/forest_admin_datasource_customizer/decorators/override/override_collection_decorator.rb +49 -0
  48. data/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb +95 -0
  49. data/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb +57 -0
  50. data/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb +268 -0
  51. data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb +70 -0
  52. data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_decorator.rb +37 -0
  53. data/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb +190 -0
  54. data/lib/forest_admin_datasource_customizer/decorators/schema/schema_collection_decorator.rb +21 -0
  55. data/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +135 -0
  56. data/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +60 -0
  57. data/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb +127 -0
  58. data/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb +82 -0
  59. data/lib/forest_admin_datasource_customizer/decorators/write/create_relations/create_relations_collection_decorator.rb +75 -0
  60. data/lib/forest_admin_datasource_customizer/decorators/write/update_relations/update_relations_collection_decorator.rb +96 -0
  61. data/lib/forest_admin_datasource_customizer/decorators/write/write_datasource_decorator.rb +14 -0
  62. data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_customization_context.rb +18 -0
  63. data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb +125 -0
  64. data/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb +27 -0
  65. data/lib/forest_admin_datasource_customizer/plugins/import_field.rb +74 -0
  66. data/lib/forest_admin_datasource_customizer/version.rb +1 -1
  67. metadata +84 -5
  68. data/README.md +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a57e523c5b7c53d6a40452a6f9ea21a7a1b858ab1a39a3f5cac3fe83ec93ec27
4
- data.tar.gz: a6cf096f51d0ed6c12fa9787e69b74c75663f1eba63981822369ba41a73bd621
3
+ metadata.gz: 26154cad59202641c5e5b05438306ca77b63a79a764a8f65d4a31a0c7808c33f
4
+ data.tar.gz: 9e9bfab31439f364e7a0e3624001a952a6f51b670fb6677cbf00cfcb476006a8
5
5
  SHA512:
6
- metadata.gz: ba6b15e85b6c7bd38cf9a32b26824b52c8078a33b0001add15942a042c8d5afd8fd4d7a3e21d742cdce700338a62f1a0b928d3d40ee8af171ae92acad415d02f
7
- data.tar.gz: 9deebebbc665d471aa7450e23df9bd12df12d0333cca89aefa1cd3cabf710a755c0821ee944d41daa8ee1437b31ce5ddb90256f53b8be8f55cd1b819618a6b2d
6
+ metadata.gz: 601b7736694dbe374753c7b8c9d02563c7a7ea4e17fc1d0eb50964942953ebcdc6bed0c373fafe058fbaaa84e80171e685db8ed3d3a4612c7c82bbdfb81a3a84
7
+ data.tar.gz: d592ca27fb7c138fa7e347d8e99fcd09a55a914e959faa7824144441163ae306201733c6c35e284a953d803a1a3344c07ae003ad04e44c226b31f52d89544b99
@@ -12,12 +12,12 @@ 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 = "MIT"
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
19
19
  spec.metadata["source_code_uri"] = "https://github.com/ForestAdmin/agent-ruby"
20
- spec.metadata["changelog_uri"] = "https://github.com/ForestAdmin/agent-ruby/CHANGELOG.md"
20
+ spec.metadata["changelog_uri"] = "https://github.com/ForestAdmin/agent-ruby/blob/main/CHANGELOG.md"
21
21
  spec.metadata["rubygems_mfa_required"] = "false"
22
22
 
23
23
  # Specify which files should be added to the gem when it is released.
@@ -34,5 +34,6 @@ admin work on any Ruby application."
34
34
  spec.require_paths = ["lib"]
35
35
 
36
36
  spec.add_dependency "activesupport", ">= 6.1"
37
+ spec.add_dependency 'marcel', '~> 1.0', '>= 1.0.4'
37
38
  spec.add_dependency "zeitwerk", "~> 2.3"
38
39
  end
@@ -1,5 +1,6 @@
1
1
  module ForestAdminDatasourceCustomizer
2
2
  class CollectionCustomizer
3
+ include ForestAdminDatasourceToolkit::Validations
3
4
  attr_reader :datasource_customizer, :stack, :name
4
5
 
5
6
  def initialize(datasource_customizer, stack, name)
@@ -8,18 +9,304 @@ module ForestAdminDatasourceCustomizer
8
9
  @name = name
9
10
  end
10
11
 
12
+ def add_action(name, definition)
13
+ push_customization { @stack.action.get_collection(@name).add_action(name, definition) }
14
+ end
15
+
11
16
  def schema
12
- @stack.datasource.get_collection(@name).schema
17
+ @stack.validation.get_collection(@name).schema
13
18
  end
14
19
 
15
20
  def collection
16
- @stack.datasource.get_collection(@name)
21
+ @stack.validation.get_collection(@name)
17
22
  end
18
23
 
19
24
  def use(plugin, options = [])
20
- push_customization(
21
- -> { plugin.run(@datasource_customizer, self, options) }
22
- )
25
+ push_customization { plugin.new.run(@datasource_customizer, self, options) }
26
+ end
27
+
28
+ def disable_count
29
+ push_customization { @stack.schema.get_collection(@name).override_schema(countable: false) }
30
+ end
31
+
32
+ def replace_search(definition)
33
+ push_customization { @stack.search.get_collection(@name).replace_search(definition) }
34
+ end
35
+
36
+ def add_field(name, definition)
37
+ push_customization do
38
+ collection_before_relations = @stack.early_computed.get_collection(@name)
39
+ collection_after_relations = @stack.late_computed.get_collection(@name)
40
+ can_be_computed_before_relations = definition.dependencies.all? do |field|
41
+ !ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema(collection_before_relations, field).nil?
42
+ rescue StandardError
43
+ false
44
+ end
45
+
46
+ collection = can_be_computed_before_relations ? collection_before_relations : collection_after_relations
47
+
48
+ collection.register_computed(name, definition)
49
+ end
50
+ end
51
+
52
+ def emulate_field_operator(name, operator)
53
+ push_customization do
54
+ collection = if @stack.early_op_emulate.get_collection(@name).schema[:fields].key?(name)
55
+ @stack.early_op_emulate.get_collection(@name)
56
+ else
57
+ @stack.late_op_emulate.get_collection(@name)
58
+ end
59
+
60
+ collection.emulate_field_operator(name, operator)
61
+ end
62
+ end
63
+
64
+ def emulate_field_filtering(name)
65
+ push_customization do
66
+ collection = @stack.late_op_emulate.get_collection(@name)
67
+ field = collection.schema[:fields][name]
68
+
69
+ if field.column_type.is_a?(String)
70
+ operators = Rules.get_allowed_operators_for_column_type[field.column_type]
71
+
72
+ operators.each do |operator|
73
+ emulate_field_operator(name, operator) unless field.filter_operators&.include?(operator)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def replace_field_operator(name, operator, &replacer)
80
+ push_customization do
81
+ collection = if @stack.early_op_emulate.get_collection(@name).schema[:fields].key?(name)
82
+ @stack.early_op_emulate.get_collection(@name)
83
+ else
84
+ @stack.late_op_emulate.get_collection(@name)
85
+ end
86
+
87
+ collection.replace_field_operator(name, operator, &replacer)
88
+ end
89
+ end
90
+
91
+ # Add a many to one relation to the collection
92
+ # @param name name of the new relation
93
+ # @param foreign_collection name of the targeted collection
94
+ # @param options extra information about the relation
95
+ # @example
96
+ # books.add_many_to_one_relation('my_author', 'persons', { foreign_key: 'author_id' })
97
+ def add_many_to_one_relation(name, foreign_collection, options = {})
98
+ push_relation(name, {
99
+ type: 'ManyToOne',
100
+ foreign_collection: foreign_collection,
101
+ foreign_key: options[:foreign_key],
102
+ foreign_key_target: options[:foreign_key_target]
103
+ })
104
+ end
105
+
106
+ # Add a one to many relation to the collection
107
+ # @param name name of the new relation
108
+ # @param foreign_collection name of the targeted collection
109
+ # @param options extra information about the relation
110
+ # @example
111
+ # persons.add_one_to_many_relation('written_books', 'books', { origin_key: 'author_id' })
112
+ def add_one_to_many_relation(name, foreign_collection, options = {})
113
+ push_relation(name, {
114
+ type: 'OneToMany',
115
+ foreign_collection: foreign_collection,
116
+ origin_key: options[:origin_key],
117
+ origin_key_target: options[:origin_key_target]
118
+ })
119
+ end
120
+
121
+ # Add a one to one relation to the collection
122
+ # @param name name of the new relation
123
+ # @param foreign_collection name of the targeted collection
124
+ # @param options extra information about the relation
125
+ # @example
126
+ # persons.add_one_to_one_relation('best_friend', 'persons', { origin_key: 'best_friend_id' })
127
+ def add_one_to_one_relation(name, foreign_collection, options = {})
128
+ push_relation(name, {
129
+ type: 'OneToOne',
130
+ foreign_collection: foreign_collection,
131
+ origin_key: options[:origin_key],
132
+ origin_key_target: options[:origin_key_target]
133
+ })
134
+ end
135
+
136
+ # Add a many to many relation to the collection
137
+ # @param name name of the new relation
138
+ # @param foreign_collection name of the targeted collection
139
+ # @param through_collection name of the intermediary collection
140
+ # @param options extra information about the relation
141
+ # @example
142
+ # dvds.add_many_to_many_relation('rentals_of_this_dvd', 'rentals', 'dvd_rentals', {
143
+ # origin_key: 'dvd_id',
144
+ # foreign_key: 'rental_id'
145
+ # })
146
+ def add_many_to_many_relation(name, foreign_collection, through_collection, options = {})
147
+ push_relation(name, {
148
+ type: 'ManyToMany',
149
+ foreign_collection: foreign_collection,
150
+ through_collection: through_collection,
151
+ origin_key: options[:origin_key],
152
+ origin_key_target: options[:origin_key_target],
153
+ foreign_key: options[:foreign_key],
154
+ foreign_key_target: options[:foreign_key_target]
155
+ })
156
+ end
157
+
158
+ def add_external_relation(name, definition)
159
+ use(ForestAdminDatasourceCustomizer::Plugins::AddExternalRelation, { name: name }.merge(definition))
160
+ end
161
+
162
+ def import_field(name, definition)
163
+ use(ForestAdminDatasourceCustomizer::Plugins::ImportField, { name: name }.merge(definition))
164
+ end
165
+
166
+ # Add a new validator to the edition form of a given field
167
+ # @param name The name of the field
168
+ # @param operator The validator that you wish to add
169
+ # @param value A configuration value that the validator may need
170
+ # @example
171
+ # .add_field_validation('first_name', Operators::LONGER_THAN, 2)
172
+ def add_field_validation(name, operator, value = nil)
173
+ push_customization do
174
+ @stack.validation.get_collection(@name).add_validation(name, { operator: operator, value: value })
175
+ end
176
+ end
177
+
178
+ # Enable sorting on a specific field using emulation.
179
+ # As for all the emulation method, the field sorting will be done in-memory.
180
+ # @param name the name of the field to enable emulation on
181
+ # @example
182
+ # .emulate_field_sorting('fullName')
183
+ def emulate_field_sorting(name)
184
+ push_customization { @stack.sort.get_collection(@name).emulate_field_sorting(name) }
185
+ end
186
+
187
+ # Replace an implementation for the sorting.
188
+ # The field sorting will be done by the datasource.
189
+ # @param name the name of the field to enable sort
190
+ # @param equivalent_sort the sort equivalent
191
+ # @example
192
+ # .replace_field_sorting(
193
+ # 'fullName',
194
+ # [
195
+ # { field: 'firstName', ascending: true },
196
+ # { field: 'lastName', ascending: true },
197
+ # ]
198
+ # )
199
+ def replace_field_sorting(name, equivalent_sort)
200
+ push_customization { @stack.sort.get_collection(@name).replace_field_sorting(name, equivalent_sort) }
201
+ end
202
+
203
+ # Remove fields from the exported schema (they will still be usable within the agent).
204
+ # @param names the names of the field or the relation
205
+ # @example
206
+ # .remove_field('fieldNameToRemove', 'relationNameToRemove')
207
+ def remove_field(*names)
208
+ push_customization do
209
+ collection = @stack.publication.get_collection(@name)
210
+ names.each { |name| collection.change_field_visibility(name, false) }
211
+ end
212
+ end
213
+
214
+ # Rename fields from the exported schema.
215
+ # @param current_name the current name of the field or the relation in a given collection
216
+ # @param new_name the new name of the field or the relation
217
+ # @example
218
+ # rename_field('currentFieldOrRelationName', 'newFieldOrRelationName')
219
+ def rename_field(current_name, new_name)
220
+ push_customization { @stack.rename_field.get_collection(@name).rename_field(current_name, new_name) }
221
+ end
222
+
223
+ # Replace the write behavior of a field.
224
+ # @param name the name of the field
225
+ # @param definition the function or a value to represent the write behavior
226
+ # @example
227
+ # .replace_field_writing('author_last_name') do
228
+ # { 'author' => { 'last_name' => value } }
229
+ # end
230
+ def replace_field_writing(name, &definition)
231
+ push_customization { @stack.write.get_collection(@name).replace_field_writing(name, &definition) }
232
+ end
233
+
234
+ # Create a new API chart
235
+ # @param name name of the chart
236
+ # @param definition definition of the chart
237
+ # @example
238
+ # .add_chart('num_customers') do |context, result_builder|
239
+ # return result_builder.distribution({
240
+ # tomatoes: 10,
241
+ # potatoes: 20,
242
+ # carrots: 30,
243
+ # });
244
+ # end
245
+ def add_chart(name, &definition)
246
+ push_customization { @stack.chart.get_collection(@name).add_chart(name, &definition) }
247
+ end
248
+
249
+ # Add a new hook handler to an action
250
+ # @param position Either if the hook is executed before or after the action
251
+ # @param type Type of action which should be hooked
252
+ # @param handler Callback that should be executed when the hook is triggered
253
+ # @example
254
+ # .add_hook('before', 'list') do |context|
255
+ # # Do something before the list action
256
+ # end
257
+ def add_hook(position, type, &handler)
258
+ push_customization { @stack.hook.get_collection(@name).add_hook(position, type, handler) }
259
+ end
260
+
261
+ # Add a new segment on the collection.
262
+ # @param name the name of the segment
263
+ # @param definition a function used to generate a condition tree or a condition tree
264
+ # @example
265
+ # .add_segment(
266
+ # 'Wrote more than 2 books',
267
+ # { field: 'booksCount', operator: 'GreaterThan', value: 2 }
268
+ # );
269
+ def add_segment(name, &definition)
270
+ push_customization { @stack.segment.get_collection(@name).add_segment(name, definition) }
271
+ end
272
+
273
+ # Choose how binary data should be transported to the GUI.
274
+ # By default, all fields are transported as 'datauri', with the exception of primary and foreign
275
+ # keys.
276
+ #
277
+ # Using 'datauri' allows to use the FilePicker widget, while 'hex' is more suitable for
278
+ # short binary data (for instance binary uuids).
279
+ #
280
+ # @param name the name of the field
281
+ # @param binary_mode either 'datauri' or 'hex'
282
+ # @example
283
+ # .replace_field_binary_mode('avatar', 'datauri');
284
+ def replace_field_binary_mode(name, binary_mode)
285
+ push_customization { @stack.binary.get_collection(@name).set_binary_mode(name, binary_mode) }
286
+ end
287
+
288
+ def override_create(&handler)
289
+ push_customization { @stack.override.get_collection(@name).add_create_handler(handler) }
290
+ end
291
+
292
+ def override_update(&handler)
293
+ push_customization { @stack.override.get_collection(@name).add_update_handler(handler) }
294
+ end
295
+
296
+ def override_delete(&handler)
297
+ push_customization { @stack.override.get_collection(@name).add_delete_handler(handler) }
298
+ end
299
+
300
+ private
301
+
302
+ def push_customization(&customization)
303
+ @stack.queue_customization(customization)
304
+
305
+ self
306
+ end
307
+
308
+ def push_relation(name, definition)
309
+ push_customization { @stack.relation.get_collection(@name).add_relation(name, definition) }
23
310
  end
24
311
  end
25
312
  end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Context
3
+ class AgentCustomizationContext
4
+ include ForestAdminDatasourceCustomizer::Context::RelaxedWrappers
5
+
6
+ attr_reader :caller
7
+
8
+ def initialize(datasource, caller)
9
+ @real_datasource = datasource
10
+ @caller = caller
11
+ end
12
+
13
+ def datasource
14
+ RelaxedDataSource.new(@real_datasource, @caller)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Context
3
+ class CollectionCustomizationContext < AgentCustomizationContext
4
+ include ForestAdminDatasourceCustomizer::Context::RelaxedWrappers
5
+ def initialize(collection, caller)
6
+ super(collection.datasource, caller)
7
+ @real_collection = collection
8
+ end
9
+
10
+ def collection
11
+ RelaxedCollection.new(@real_collection, @caller)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Context
3
+ module RelaxedWrappers
4
+ class RelaxedCollection
5
+ include ForestAdminDatasourceToolkit::Components::Query
6
+
7
+ def initialize(collection, caller)
8
+ @collection = collection
9
+ @caller = caller
10
+ end
11
+
12
+ def native_driver
13
+ @collection.native_driver
14
+ end
15
+
16
+ def schema
17
+ @collection.schema
18
+ end
19
+
20
+ def execute(name, form_values = {}, filter = nil)
21
+ @collection.execute(@caller, name, form_values, filter)
22
+ end
23
+
24
+ def get_form(name, data = nil, filter = nil, metas = {})
25
+ @collection.get_form(@caller, name, data, filter, metas)
26
+ end
27
+
28
+ def create(data)
29
+ @collection.create(@caller, data)
30
+ end
31
+
32
+ def list(filter, projection)
33
+ @collection.list(@caller, filter, projection)
34
+ end
35
+
36
+ def update(filter, data)
37
+ @collection.update(@caller, filter, data)
38
+ end
39
+
40
+ def delete(filter)
41
+ @collection.delete(@caller, filter)
42
+ end
43
+
44
+ def aggregate(filter, aggregation, limit = nil)
45
+ @collection.aggregate(@caller, filter, aggregation, limit)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Context
3
+ module RelaxedWrappers
4
+ class RelaxedDataSource
5
+ def initialize(datasource, caller)
6
+ @real_datasource = datasource
7
+ @caller = caller
8
+ end
9
+
10
+ # Get a collection from a datasource
11
+ # @param name the name of the collection
12
+ def get_collection(name)
13
+ RelaxedCollection.new(@real_datasource.get_collection(name), @caller)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -8,7 +8,7 @@ module ForestAdminDatasourceCustomizer
8
8
  end
9
9
 
10
10
  def schema
11
- @stack.datasource.schema
11
+ @stack.validation.schema
12
12
  end
13
13
 
14
14
  def get_collection(name)
@@ -19,33 +19,63 @@ module ForestAdminDatasourceCustomizer
19
19
  @stack.datasource.collections.transform_values { |collection| get_collection(collection.name) }
20
20
  end
21
21
 
22
- def datasource
23
- # TODO: call @stack.apply_queued_customizations(logger);
22
+ def datasource(logger)
23
+ @stack.apply_queued_customizations(logger)
24
24
 
25
25
  @stack.datasource
26
26
  end
27
27
 
28
- def add_datasource(datasource, _options)
29
- # TODO: add include/exclude behavior
30
- # TODO: add rename behavior
28
+ def add_datasource(datasource, options)
29
+ @stack.queue_customization(lambda {
30
+ if options[:include] || options[:exclude]
31
+ publication_decorator = Decorators::Publication::PublicationDatasourceDecorator.new(datasource)
32
+ publication_decorator.keep_collections_matching(options[:include], options[:exclude])
33
+ datasource = publication_decorator
34
+ end
31
35
 
32
- datasource.collections.each_value { |collection| @composite_datasource.add_collection(collection) }
36
+ if options[:rename]
37
+ rename_collection_decorator = Decorators::RenameCollection::RenameCollectionDatasourceDecorator.new(
38
+ datasource
39
+ )
40
+ rename_collection_decorator.rename_collections(options[:rename])
41
+ datasource = rename_collection_decorator
42
+ end
43
+
44
+ datasource.collections.each_value do |collection|
45
+ @composite_datasource.add_collection(collection)
46
+ end
47
+ })
48
+
49
+ self
33
50
  end
34
51
 
35
- def add_chart(name, definition)
36
- # TODO: to implement
52
+ # Create a new API chart
53
+ # @param name name of the chart
54
+ # @param definition definition of the chart
55
+ # @example
56
+ # .addChart('num_customers') { |context, result_builder| result_builder.value(123) }
57
+ def add_chart(name, &definition)
58
+ push_customization { @stack.chart.add_chart(name, &definition) }
37
59
  end
38
60
 
39
61
  def use(plugin, options)
40
- # TODO: to implement
62
+ push_customization { plugin.new.run(self, nil, options) }
41
63
  end
42
64
 
43
65
  def customize_collection(name, handle)
44
- # TODO: to implement
66
+ handle.call(get_collection(name))
67
+ end
68
+
69
+ def remove_collection(*names)
70
+ @stack.queue_customization(-> { @stack.publication.keep_collections_matching(nil, names) })
71
+
72
+ self
45
73
  end
46
74
 
47
- def remove_collection(names)
48
- # TODO: to implement
75
+ private
76
+
77
+ def push_customization(&customization)
78
+ @stack.queue_customization(customization)
49
79
  end
50
80
  end
51
81
  end
@@ -0,0 +1,137 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module Action
4
+ class ActionCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Components
6
+
7
+ def initialize(child_collection, datasource)
8
+ super
9
+ @actions = {}
10
+ end
11
+
12
+ def add_action(name, action)
13
+ action.build_fields
14
+ @actions[name] = action
15
+
16
+ mark_schema_as_dirty
17
+ end
18
+
19
+ def execute(caller, name, data, filter = nil)
20
+ action = @actions[name]
21
+ return @child_collection.execute(caller, name, data, filter) if action.nil?
22
+
23
+ context = get_context(caller, action, data, filter)
24
+
25
+ result_builder = ResultBuilder.new
26
+ result = action.execute.call(context, result_builder)
27
+
28
+ return result if result.is_a? Hash
29
+
30
+ result_builder.success
31
+ end
32
+
33
+ def get_form(caller, name, data = nil, filter = nil, metas = {})
34
+ action = @actions[name]
35
+ return @child_collection.get_form(caller, name, data, filter, metas) if action.nil?
36
+ return [] if action.form.nil?
37
+
38
+ form_values = data || {}
39
+ used = []
40
+ context = get_context(caller, action, form_values, filter, used, metas[:change_field])
41
+
42
+ dynamic_fields = action.form
43
+ if metas[:search_field]
44
+ # in the case of a search hook,
45
+ # we don't want to rebuild all the fields. only the one searched
46
+ dynamic_fields = dynamic_fields.select { |field| field.label == metas[:search_field] }
47
+ end
48
+ dynamic_fields = drop_defaults(context, dynamic_fields, form_values)
49
+ dynamic_fields = drop_ifs(context, dynamic_fields) unless metas[:include_hidden_fields]
50
+
51
+ fields = drop_deferred(context, metas[:search_values], dynamic_fields)
52
+
53
+ fields.each do |field|
54
+ if field.value.nil?
55
+ # customer did not define a handler to rewrite the previous value => reuse current one.
56
+ field.value = form_values[field.label]
57
+ end
58
+
59
+ # fields that were accessed through the context.get_form_value(x) getter should be watched.
60
+ field.watch_changes = used.include?(field.label)
61
+ end
62
+
63
+ fields
64
+ end
65
+
66
+ def refine_schema(sub_schema)
67
+ sub_schema[:actions] = @actions
68
+
69
+ sub_schema
70
+ end
71
+
72
+ private
73
+
74
+ def drop_defaults(context, fields, data)
75
+ unvalued_fields = fields.reject { |field| data.key?(field.label) }
76
+ defaults = unvalued_fields.map { |field| evaluate(context, field.default_value) }
77
+ unvalued_fields.each_with_index { |field, index| data[field.label] = defaults[index] }
78
+
79
+ fields.each { |field| field.default_value = nil }
80
+
81
+ fields
82
+ end
83
+
84
+ def drop_ifs(context, fields)
85
+ if_values = fields.map { |field| !field.if_condition || evaluate(context, field.if_condition) }
86
+ new_fields = fields.select.with_index { |_field, index| if_values[index] }
87
+ new_fields.each do |field|
88
+ field = field.dup
89
+ field.if_condition = nil
90
+ end
91
+
92
+ new_fields
93
+ end
94
+
95
+ def drop_deferred(context, search_values, fields)
96
+ new_fields = []
97
+ fields.each do |field|
98
+ field = field.dup
99
+ field.instance_variables.each do |key|
100
+ key = key.to_s.delete('@').to_sym
101
+
102
+ next unless field.respond_to?(key)
103
+
104
+ # call getter corresponding to the key and then set the evaluated value
105
+ value = field.send(key)
106
+ key = key.to_s.concat('=').to_sym
107
+
108
+ field.send(key, evaluate(context, value, search_values&.dig(field.label)))
109
+ end
110
+
111
+ new_fields << Actions::ActionFieldFactory.build(field.to_h)
112
+ end
113
+
114
+ new_fields
115
+ end
116
+
117
+ def evaluate(context, value, search_value = nil)
118
+ if value.respond_to?(:call)
119
+ return value.call(context, search_value) if search_value
120
+
121
+ return value.call(context)
122
+ end
123
+
124
+ value
125
+ end
126
+
127
+ def get_context(caller, action, form_values = [], filter = nil, used = [], change_field = nil)
128
+ if action.scope == Types::ActionScope::SINGLE
129
+ return Context::ActionContextSingle.new(self, caller, form_values, filter, used, change_field)
130
+ end
131
+
132
+ Context::ActionContext.new(self, caller, form_values, filter, used, change_field)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end