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.
- checksums.yaml +4 -4
- data/forest_admin_datasource_customizer.gemspec +3 -2
- data/lib/forest_admin_datasource_customizer/collection_customizer.rb +292 -5
- data/lib/forest_admin_datasource_customizer/context/agent_customization_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/context/collection_customization_context.rb +15 -0
- data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_collection.rb +50 -0
- data/lib/forest_admin_datasource_customizer/context/relaxed_wrappers/relaxed_data_source.rb +18 -0
- data/lib/forest_admin_datasource_customizer/datasource_customizer.rb +43 -13
- data/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +137 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +67 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context.rb +56 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/context/action_context_single.rb +26 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb +50 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/result_builder.rb +68 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/types/action_scope.rb +15 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb +35 -0
- data/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb +357 -0
- data/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb +215 -0
- data/lib/forest_admin_datasource_customizer/decorators/binary/binary_helper.rb +17 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb +41 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb +33 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb +46 -0
- data/lib/forest_admin_datasource_customizer/decorators/chart/result_builder.rb +148 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb +115 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/computed_definition.rb +21 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/utils/computed_field.rb +74 -0
- data/lib/forest_admin_datasource_customizer/decorators/computed/utils/flattener.rb +49 -0
- data/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb +33 -4
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_aggregate_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_create_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_delete_context.rb +12 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_list_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_update_context.rb +12 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_aggregate_context.rb +20 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_create_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_delete_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_list_context.rb +19 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/before/hook_before_update_context.rb +19 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/context/hook_context.rb +22 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/hook_collection_decorator.rb +95 -0
- data/lib/forest_admin_datasource_customizer/decorators/hook/hooks.rb +26 -0
- data/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb +118 -0
- data/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb +50 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb +16 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb +16 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb +17 -0
- data/lib/forest_admin_datasource_customizer/decorators/override/override_collection_decorator.rb +49 -0
- data/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb +95 -0
- data/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb +57 -0
- data/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb +268 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb +70 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_decorator.rb +37 -0
- data/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb +190 -0
- data/lib/forest_admin_datasource_customizer/decorators/schema/schema_collection_decorator.rb +21 -0
- data/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb +135 -0
- data/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +60 -0
- data/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb +127 -0
- data/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb +82 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/create_relations/create_relations_collection_decorator.rb +75 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/update_relations/update_relations_collection_decorator.rb +96 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_datasource_decorator.rb +14 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_customization_context.rb +18 -0
- data/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb +125 -0
- data/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb +27 -0
- data/lib/forest_admin_datasource_customizer/plugins/import_field.rb +74 -0
- data/lib/forest_admin_datasource_customizer/version.rb +1 -1
- metadata +84 -5
- data/README.md +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26154cad59202641c5e5b05438306ca77b63a79a764a8f65d4a31a0c7808c33f
|
4
|
+
data.tar.gz: 9e9bfab31439f364e7a0e3624001a952a6f51b670fb6677cbf00cfcb476006a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 = "
|
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.
|
17
|
+
@stack.validation.get_collection(@name).schema
|
13
18
|
end
|
14
19
|
|
15
20
|
def collection
|
16
|
-
@stack.
|
21
|
+
@stack.validation.get_collection(@name)
|
17
22
|
end
|
18
23
|
|
19
24
|
def use(plugin, options = [])
|
20
|
-
push_customization(
|
21
|
-
|
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.
|
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
|
-
|
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,
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
62
|
+
push_customization { plugin.new.run(self, nil, options) }
|
41
63
|
end
|
42
64
|
|
43
65
|
def customize_collection(name, handle)
|
44
|
-
|
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
|
-
|
48
|
-
|
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
|