forest_admin_datasource_customizer 1.0.0.pre.beta.21 → 1.0.0.pre.beta.57
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/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
|