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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Computed
|
|
4
|
+
module Utils
|
|
5
|
+
class ComputedField
|
|
6
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
|
7
|
+
|
|
8
|
+
def self.compute_field(ctx, computed, computed_dependencies, flatten)
|
|
9
|
+
transform_unique_values(
|
|
10
|
+
Flattener.un_flatten(
|
|
11
|
+
flatten,
|
|
12
|
+
Projection.new(computed_dependencies)
|
|
13
|
+
),
|
|
14
|
+
->(unique_partials) { computed.get_values(unique_partials, ctx) }
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.queue_field(ctx, collection, new_path, paths, flatten)
|
|
19
|
+
return if paths.include?(new_path)
|
|
20
|
+
|
|
21
|
+
computed = collection.get_computed(new_path)
|
|
22
|
+
nested_dependencies = Projection.new(computed.dependencies)
|
|
23
|
+
.nest(prefix: new_path.include?(':') ? new_path.split(':')[0] : nil)
|
|
24
|
+
|
|
25
|
+
nested_dependencies.each do |path|
|
|
26
|
+
queue_field(ctx, collection, path, paths, flatten)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
dependency_values = nested_dependencies.map { |path| flatten[paths.index(path)] }
|
|
30
|
+
paths.push(new_path)
|
|
31
|
+
|
|
32
|
+
flatten << compute_field(ctx, computed, computed.dependencies, dependency_values)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.compute_from_records(ctx, collection, records_projection, desired_projection, records)
|
|
36
|
+
paths = records_projection.clone
|
|
37
|
+
flatten = Flattener.flatten(records, paths)
|
|
38
|
+
|
|
39
|
+
desired_projection.each do |path|
|
|
40
|
+
queue_field(ctx, collection, path, paths, flatten)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Flattener.un_flatten(desired_projection.map { |path| flatten[paths.index(path)] }, desired_projection)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.transform_unique_values(inputs, callback)
|
|
47
|
+
indexes = {}
|
|
48
|
+
mapping = []
|
|
49
|
+
unique_inputs = []
|
|
50
|
+
|
|
51
|
+
inputs.each do |input|
|
|
52
|
+
if input
|
|
53
|
+
hash = Digest::SHA1.hexdigest(input.to_s)
|
|
54
|
+
|
|
55
|
+
if indexes[hash].nil?
|
|
56
|
+
indexes[hash] = unique_inputs.length
|
|
57
|
+
unique_inputs.push(input)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
mapping.push(indexes[hash])
|
|
61
|
+
else
|
|
62
|
+
mapping.push(-1)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unique_outputs = callback.call(unique_inputs)
|
|
67
|
+
|
|
68
|
+
mapping.map { |index| index == -1 ? nil : unique_outputs[index] }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Computed
|
|
4
|
+
module Utils
|
|
5
|
+
class Flattener
|
|
6
|
+
def self.flatten(records, projection)
|
|
7
|
+
projection.map do |field|
|
|
8
|
+
records.map { |record| ForestAdminDatasourceToolkit::Utils::Record.field_value(record, field) }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.un_flatten(flatten, projection)
|
|
13
|
+
num_records = flatten[0]&.length || 0
|
|
14
|
+
records = []
|
|
15
|
+
|
|
16
|
+
(0...num_records).each do |record_index|
|
|
17
|
+
records[record_index] = {}
|
|
18
|
+
|
|
19
|
+
projection.each_with_index do |path, path_index|
|
|
20
|
+
parts = path.split(':').reject { |part| part.nil? || part.empty? }
|
|
21
|
+
value = flatten[path_index][record_index]
|
|
22
|
+
|
|
23
|
+
# Ignore undefined values.
|
|
24
|
+
next if value.nil?
|
|
25
|
+
|
|
26
|
+
# Set all others (including null)
|
|
27
|
+
record = records[record_index]
|
|
28
|
+
|
|
29
|
+
(0...parts.length).each do |part_index|
|
|
30
|
+
part = parts[part_index]
|
|
31
|
+
|
|
32
|
+
if part_index == parts.length - 1
|
|
33
|
+
record[part] = value
|
|
34
|
+
elsif record[part].nil?
|
|
35
|
+
record[part] = {}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
record = record[part]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
records
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -3,11 +3,40 @@ module ForestAdminDatasourceCustomizer
|
|
|
3
3
|
class DecoratorsStack
|
|
4
4
|
include ForestAdminDatasourceToolkit::Decorators
|
|
5
5
|
|
|
6
|
-
attr_reader :datasource, :
|
|
6
|
+
attr_reader :datasource, :schema, :search, :early_computed, :late_computed, :action, :relation, :late_op_emulate,
|
|
7
|
+
:early_op_emulate, :validation, :sort, :rename_field, :publication, :write, :chart, :hook, :segment,
|
|
8
|
+
:binary, :override
|
|
7
9
|
|
|
8
10
|
def initialize(datasource)
|
|
11
|
+
@customizations = []
|
|
12
|
+
|
|
9
13
|
last = datasource
|
|
10
|
-
last = @
|
|
14
|
+
last = @override = DatasourceDecorator.new(last, Override::OverrideCollectionDecorator)
|
|
15
|
+
last = DatasourceDecorator.new(last, Empty::EmptyCollectionDecorator)
|
|
16
|
+
last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
|
|
17
|
+
|
|
18
|
+
last = @early_computed = DatasourceDecorator.new(last, Computed::ComputeCollectionDecorator)
|
|
19
|
+
last = @early_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
|
|
20
|
+
last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
|
|
21
|
+
last = @relation = DatasourceDecorator.new(last, Relation::RelationCollectionDecorator)
|
|
22
|
+
last = @late_computed = DatasourceDecorator.new(last, Computed::ComputeCollectionDecorator)
|
|
23
|
+
last = @late_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
|
|
24
|
+
last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
|
|
25
|
+
|
|
26
|
+
last = @search = DatasourceDecorator.new(last, Search::SearchCollectionDecorator)
|
|
27
|
+
last = @segment = DatasourceDecorator.new(last, Segment::SegmentCollectionDecorator)
|
|
28
|
+
last = @sort = DatasourceDecorator.new(last, Sort::SortCollectionDecorator)
|
|
29
|
+
|
|
30
|
+
last = @chart = Chart::ChartDatasourceDecorator.new(last)
|
|
31
|
+
last = @action = DatasourceDecorator.new(last, Action::ActionCollectionDecorator)
|
|
32
|
+
last = @schema = DatasourceDecorator.new(last, Schema::SchemaCollectionDecorator)
|
|
33
|
+
last = @write = Write::WriteDatasourceDecorator.new(last)
|
|
34
|
+
last = @hook = DatasourceDecorator.new(last, Hook::HookCollectionDecorator)
|
|
35
|
+
last = @validation = DatasourceDecorator.new(last, Validation::ValidationCollectionDecorator)
|
|
36
|
+
last = @binary = DatasourceDecorator.new(last, Binary::BinaryCollectionDecorator)
|
|
37
|
+
|
|
38
|
+
last = @publication = Publication::PublicationDatasourceDecorator.new(last)
|
|
39
|
+
last = @rename_field = DatasourceDecorator.new(last, RenameField::RenameFieldCollectionDecorator)
|
|
11
40
|
@datasource = last
|
|
12
41
|
end
|
|
13
42
|
|
|
@@ -21,11 +50,11 @@ module ForestAdminDatasourceCustomizer
|
|
|
21
50
|
# This method will be called recursively and clears the queue at each recursion to ensure
|
|
22
51
|
# that all customizations are applied in the right order.
|
|
23
52
|
def apply_queued_customizations(logger)
|
|
24
|
-
queued_customizations = @customizations.
|
|
53
|
+
queued_customizations = @customizations.clone
|
|
25
54
|
@customizations = []
|
|
26
55
|
|
|
27
56
|
while queued_customizations.length.positive?
|
|
28
|
-
queued_customizations.shift.call
|
|
57
|
+
queued_customizations.shift.call
|
|
29
58
|
apply_queued_customizations(logger)
|
|
30
59
|
end
|
|
31
60
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module After
|
|
6
|
+
class HookAfterAggregateContext < Hook::Context::Before::HookBeforeAggregateContext
|
|
7
|
+
attr_reader :aggregate_result
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter, aggregation, aggregate_result, limit = nil)
|
|
10
|
+
super(collection, caller, filter, aggregation, limit)
|
|
11
|
+
@aggregate_result = aggregate_result
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module After
|
|
6
|
+
class HookAfterCreateContext < Hook::Context::Before::HookBeforeCreateContext
|
|
7
|
+
attr_reader :record
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, data, record)
|
|
10
|
+
super(collection, caller, data)
|
|
11
|
+
@record = record
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/forest_admin_datasource_customizer/decorators/hook/context/after/hook_after_list_context.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module After
|
|
6
|
+
class HookAfterListContext < Hook::Context::Before::HookBeforeListContext
|
|
7
|
+
attr_reader :records
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter, projection, records)
|
|
10
|
+
super(collection, caller, filter, projection)
|
|
11
|
+
@records = records
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module Before
|
|
6
|
+
class HookBeforeAggregateContext < Hook::Context::HookContext
|
|
7
|
+
attr_reader :filter, :aggregation, :limit
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter, aggregation, limit = nil)
|
|
10
|
+
super(collection, caller)
|
|
11
|
+
@filter = filter
|
|
12
|
+
@aggregation = aggregation
|
|
13
|
+
@limit = limit
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module Before
|
|
6
|
+
class HookBeforeCreateContext < Hook::Context::HookContext
|
|
7
|
+
attr_reader :data
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, data)
|
|
10
|
+
super(collection, caller)
|
|
11
|
+
@data = data
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module Before
|
|
6
|
+
class HookBeforeDeleteContext < Hook::Context::HookContext
|
|
7
|
+
attr_reader :filter
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter)
|
|
10
|
+
super(collection, caller)
|
|
11
|
+
@filter = filter
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module Before
|
|
6
|
+
class HookBeforeListContext < Hook::Context::HookContext
|
|
7
|
+
attr_reader :filter, :projection
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter, projection)
|
|
10
|
+
super(collection, caller)
|
|
11
|
+
@filter = filter
|
|
12
|
+
@projection = projection
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
module Before
|
|
6
|
+
class HookBeforeUpdateContext < Hook::Context::HookContext
|
|
7
|
+
attr_reader :filter, :patch
|
|
8
|
+
|
|
9
|
+
def initialize(collection, caller, filter, patch)
|
|
10
|
+
super(collection, caller)
|
|
11
|
+
@filter = filter
|
|
12
|
+
@patch = patch
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
module Context
|
|
5
|
+
class HookContext < ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext
|
|
6
|
+
include ForestAdminAgent::Http::Exceptions
|
|
7
|
+
def raise_validation_error(message)
|
|
8
|
+
raise ValidationError, message
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def raise_forbidden_error(message)
|
|
12
|
+
raise ForbiddenError, message
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def raise_error(message)
|
|
16
|
+
raise UnprocessableError, message
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
class HookCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
|
|
5
|
+
include ForestAdminDatasourceToolkit::Components
|
|
6
|
+
include Context
|
|
7
|
+
|
|
8
|
+
attr_reader :hooks
|
|
9
|
+
|
|
10
|
+
def initialize(child_collection, datasource)
|
|
11
|
+
super
|
|
12
|
+
@hooks = {
|
|
13
|
+
'List' => Hooks.new,
|
|
14
|
+
'Create' => Hooks.new,
|
|
15
|
+
'Update' => Hooks.new,
|
|
16
|
+
'Delete' => Hooks.new,
|
|
17
|
+
'Aggregate' => Hooks.new
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_hook(position, type, hook)
|
|
22
|
+
@hooks[type].add_handler(position, hook)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create(caller, data)
|
|
26
|
+
before_context = Before::HookBeforeCreateContext.new(@child_collection, caller, data)
|
|
27
|
+
@hooks['Create'].execute_before(before_context)
|
|
28
|
+
|
|
29
|
+
record = @child_collection.create(caller, before_context.data)
|
|
30
|
+
|
|
31
|
+
after_context = After::HookAfterCreateContext.new(@child_collection, caller, data, record)
|
|
32
|
+
@hooks['Create'].execute_after(after_context)
|
|
33
|
+
|
|
34
|
+
record
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def list(caller, filter, projection)
|
|
38
|
+
before_context = Before::HookBeforeListContext.new(@child_collection, caller, filter, projection)
|
|
39
|
+
@hooks['List'].execute_before(before_context)
|
|
40
|
+
|
|
41
|
+
records = @child_collection.list(caller, before_context.filter, before_context.projection)
|
|
42
|
+
|
|
43
|
+
after_context = After::HookAfterListContext.new(@child_collection, caller, filter, projection, records)
|
|
44
|
+
@hooks['List'].execute_after(after_context)
|
|
45
|
+
|
|
46
|
+
records
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def update(caller, filter, patch)
|
|
50
|
+
before_context = Before::HookBeforeUpdateContext.new(@child_collection, caller, filter, patch)
|
|
51
|
+
@hooks['Update'].execute_before(before_context)
|
|
52
|
+
|
|
53
|
+
@child_collection.update(caller, before_context.filter, before_context.patch)
|
|
54
|
+
|
|
55
|
+
after_context = After::HookAfterUpdateContext.new(@child_collection, caller, filter, patch)
|
|
56
|
+
@hooks['Update'].execute_after(after_context)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete(caller, filter)
|
|
60
|
+
before_context = Before::HookBeforeDeleteContext.new(@child_collection, caller, filter)
|
|
61
|
+
@hooks['Delete'].execute_before(before_context)
|
|
62
|
+
|
|
63
|
+
@child_collection.delete(caller, before_context.filter)
|
|
64
|
+
|
|
65
|
+
after_context = After::HookAfterDeleteContext.new(@child_collection, caller, filter)
|
|
66
|
+
@hooks['Delete'].execute_after(after_context)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def aggregate(caller, filter, aggregation, limit = nil)
|
|
70
|
+
before_context = Before::HookBeforeAggregateContext.new(@child_collection, caller, filter, aggregation, limit)
|
|
71
|
+
@hooks['Aggregate'].execute_before(before_context)
|
|
72
|
+
|
|
73
|
+
results = @child_collection.aggregate(
|
|
74
|
+
caller,
|
|
75
|
+
before_context.filter,
|
|
76
|
+
before_context.aggregation,
|
|
77
|
+
before_context.limit
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
after_context = After::HookAfterAggregateContext.new(
|
|
81
|
+
@child_collection,
|
|
82
|
+
caller,
|
|
83
|
+
filter,
|
|
84
|
+
aggregation,
|
|
85
|
+
results,
|
|
86
|
+
limit
|
|
87
|
+
)
|
|
88
|
+
@hooks['Aggregate'].execute_after(after_context)
|
|
89
|
+
|
|
90
|
+
results
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module Hook
|
|
4
|
+
class Hooks
|
|
5
|
+
attr_reader :before, :after
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@before = []
|
|
9
|
+
@after = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute_before(context)
|
|
13
|
+
@before.each { |hook| hook.call(context) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute_after(context)
|
|
17
|
+
@after.each { |hook| hook.call(context) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_handler(position, hook)
|
|
21
|
+
position == 'After' ? @after << hook : @before << hook
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module OperatorsEmulate
|
|
4
|
+
class OperatorsEmulateCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
|
|
5
|
+
include ForestAdminDatasourceToolkit
|
|
6
|
+
include ForestAdminDatasourceToolkit::Decorators
|
|
7
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
|
8
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
|
9
|
+
|
|
10
|
+
attr_accessor :fields
|
|
11
|
+
|
|
12
|
+
def initialize(child_collection, datasource)
|
|
13
|
+
super
|
|
14
|
+
@fields = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def emulate_field_operator(name, operator)
|
|
18
|
+
replace_field_operator(name, operator)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def replace_field_operator(name, operator, &replace_by)
|
|
22
|
+
# Check that the collection can actually support our rewriting
|
|
23
|
+
pks = Utils::Schema.primary_keys(child_collection)
|
|
24
|
+
pks.each do |pk|
|
|
25
|
+
schema = child_collection.schema[:fields][pk]
|
|
26
|
+
operators = schema.filter_operators
|
|
27
|
+
|
|
28
|
+
if !operators.include?(Operators::EQUAL) || !operators.include?(Operators::IN)
|
|
29
|
+
raise Exceptions::ForestException, "Cannot override operators on collection #{self.name}: " \
|
|
30
|
+
"the primary key columns must support 'Equal' and 'In' operators."
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check that targeted field is valid
|
|
35
|
+
field = child_collection.schema[:fields][name]
|
|
36
|
+
Validations::FieldValidator.validate(self, name)
|
|
37
|
+
unless field.is_a?(ForestAdminDatasourceToolkit::Schema::ColumnSchema)
|
|
38
|
+
raise Exceptions::ForestException, 'Cannot replace operator for relation'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Mark the field operator as replaced.
|
|
42
|
+
fields[name] = {} unless fields.key?(name)
|
|
43
|
+
fields[name][operator] = replace_by
|
|
44
|
+
mark_schema_as_dirty
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def refine_schema(sub_schema)
|
|
50
|
+
sub_schema[:fields].map do |name, schema|
|
|
51
|
+
schema.filter_operators = schema.filter_operators.union(fields[name].keys) if fields.key?(name)
|
|
52
|
+
|
|
53
|
+
schema
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
sub_schema
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def refine_filter(caller, filter = nil)
|
|
60
|
+
filter&.override(
|
|
61
|
+
condition_tree: filter.condition_tree&.replace_leafs do |leaf|
|
|
62
|
+
replace_leaf(caller, leaf, [])
|
|
63
|
+
end
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def replace_leaf(caller, leaf, replacements)
|
|
68
|
+
# ConditionTree is targeting a field on another collection => recurse.
|
|
69
|
+
if leaf.field.include?(':')
|
|
70
|
+
prefix = leaf.field.split(':').first
|
|
71
|
+
relation_schema = schema[:fields][prefix]
|
|
72
|
+
association = datasource.get_collection(relation_schema.foreign_collection)
|
|
73
|
+
association_leaf = leaf.unnest.replace_leafs do |sub_leaf|
|
|
74
|
+
association.replace_leaf(caller, sub_leaf, replacements)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return association_leaf.nest(prefix)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
fields[leaf.field]&.key?(leaf.operator) ? compute_equivalent(caller, leaf, replacements) : leaf
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def compute_equivalent(caller, leaf, replacements)
|
|
84
|
+
handler = fields.dig(leaf.field, leaf.operator)
|
|
85
|
+
if handler
|
|
86
|
+
replacement_id = "#{name}.#{leaf.field}[#{leaf.operator}]"
|
|
87
|
+
sub_replacements = replacements.union([replacement_id])
|
|
88
|
+
if replacements.include?(replacement_id)
|
|
89
|
+
raise Exceptions::ForestException, "Operator replacement cycle: #{sub_replacements.join(" -> ")}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
result = handler.call(leaf.value, Context::CollectionCustomizationContext.new(self, caller))
|
|
93
|
+
|
|
94
|
+
if result
|
|
95
|
+
equivalent = result.class < Nodes::ConditionTree ? result : ConditionTreeFactory.from_plain_object(result)
|
|
96
|
+
equivalent.replace_leafs do |sub_leaf|
|
|
97
|
+
replace_leaf(caller, sub_leaf, sub_replacements)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Validations::ConditionTreeValidator.validate(equivalent, self)
|
|
101
|
+
|
|
102
|
+
return equivalent
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
ConditionTreeFactory.match_records(
|
|
107
|
+
self,
|
|
108
|
+
leaf.apply(
|
|
109
|
+
list(caller, Filter.new, leaf.projection.with_pks(self)),
|
|
110
|
+
self,
|
|
111
|
+
caller.timezone
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module ForestAdminDatasourceCustomizer
|
|
2
|
+
module Decorators
|
|
3
|
+
module OperatorsEquivalence
|
|
4
|
+
class OperatorsEquivalenceCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
|
|
5
|
+
include ForestAdminDatasourceToolkit::Decorators
|
|
6
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def refine_schema(sub_schema)
|
|
11
|
+
schema = sub_schema.dup
|
|
12
|
+
schema[:fields] = sub_schema[:fields].dup
|
|
13
|
+
|
|
14
|
+
schema[:fields].map do |_name, field_schema|
|
|
15
|
+
if field_schema.type == 'Column'
|
|
16
|
+
new_operators = Operators.all.select do |operator|
|
|
17
|
+
ConditionTreeEquivalent.equivalent_tree?(operator, field_schema.filter_operators,
|
|
18
|
+
field_schema.column_type)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
field_schema.filter_operators = new_operators
|
|
22
|
+
else
|
|
23
|
+
field_schema
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
schema
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def refine_filter(caller, filter = nil)
|
|
31
|
+
filter&.override(
|
|
32
|
+
condition_tree: filter.condition_tree&.replace_leafs do |leaf|
|
|
33
|
+
schema = ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema(
|
|
34
|
+
@child_collection,
|
|
35
|
+
leaf.field
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
ConditionTreeEquivalent.get_equivalent_tree(
|
|
39
|
+
leaf,
|
|
40
|
+
schema.filter_operators,
|
|
41
|
+
schema.column_type,
|
|
42
|
+
caller.timezone
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|