forest_admin_datasource_customizer 1.0.0.pre.beta.86 → 1.0.0.pre.beta.88

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98b615fe1c829b8a99d8c2dac3bb852c09a669ae328ff353746c55f6f61c152f
4
- data.tar.gz: ea82667001dfcd0e5e37b39feebce3a7a368188eaf55235312dd5a48bc92e6a1
3
+ metadata.gz: 8c7120cf1ef2b8e044dfe6e93646c75be81f8b8e9fd28b4e1c157f03db1d705a
4
+ data.tar.gz: 99c70deb71c742d033bae93fcebcd028dfbece9d4eae804e51d62b40da16ecbd
5
5
  SHA512:
6
- metadata.gz: be6a34cd0435042dab88b00b583ff2ef064e01562391fd10f4bbbf5799b4fe478593fbb9efdc0e248879fbbe87e34c7e21ad2ed7bfca2becc4be28e8de3647fe
7
- data.tar.gz: 9e16a66fb986e7be63cf7be72969f2e79ac565cdc752e0123585a19ede2401a0346dbd523d3cbbf630597cf6feb4a401a677f563592797e6e016869b5c5e5a4b
6
+ metadata.gz: 3cdc27aba86bf2925c5db8cb0be2b105702352fbbb9a7a1b1fd91193189d250f819a097397eae438ec92d6dceba388ab622d441f84a353c99d90acdb88a6dfd3
7
+ data.tar.gz: 4e747e188ed52a3403bbea8a74fb01e086e32ac283436178535bce196c52e6e682a2fad589dc167f2d54f2d80b7d3d324281547a91afbac2f907432ae74f1d4f
@@ -33,6 +33,13 @@ module ForestAdminDatasourceCustomizer
33
33
  push_customization { @stack.search.get_collection(@name).replace_search(definition) }
34
34
  end
35
35
 
36
+ # Disable the search bar
37
+ # Example:
38
+ # collection.disable_search
39
+ def disable_search
40
+ push_customization { @stack.search.get_collection(@name).disable_search }
41
+ end
42
+
36
43
  def add_field(name, definition)
37
44
  push_customization do
38
45
  collection_before_relations = @stack.early_computed.get_collection(@name)
@@ -5,7 +5,7 @@ module ForestAdminDatasourceCustomizer
5
5
 
6
6
  attr_reader :datasource, :schema, :search, :early_computed, :late_computed, :action, :relation, :late_op_emulate,
7
7
  :early_op_emulate, :validation, :sort, :rename_field, :publication, :write, :chart, :hook, :segment,
8
- :binary, :override
8
+ :binary, :override, :lazy_join
9
9
 
10
10
  def initialize(datasource)
11
11
  @customizations = []
@@ -19,6 +19,8 @@ module ForestAdminDatasourceCustomizer
19
19
  last = @early_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
20
20
  last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
21
21
  last = @relation = DatasourceDecorator.new(last, Relation::RelationCollectionDecorator)
22
+ # lazy join is just before relation, to avoid relations to do useless stuff
23
+ last = @lazy_join = DatasourceDecorator.new(last, LazyJoin::LazyJoinCollectionDecorator)
22
24
  last = @late_computed = DatasourceDecorator.new(last, Computed::ComputeCollectionDecorator)
23
25
  last = @late_op_emulate = DatasourceDecorator.new(last, OperatorsEmulate::OperatorsEmulateCollectionDecorator)
24
26
  last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator)
@@ -0,0 +1,129 @@
1
+ module ForestAdminDatasourceCustomizer
2
+ module Decorators
3
+ module LazyJoin
4
+ class LazyJoinCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
5
+ include ForestAdminDatasourceToolkit::Decorators
6
+ include ForestAdminDatasourceToolkit::Components::Query
7
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
8
+
9
+ def list(caller, filter, projection)
10
+ simplified_projection = get_projection_without_useless_joins(projection)
11
+ refined_filter = refine_filter(caller, filter)
12
+ records = child_collection.list(caller, refined_filter, simplified_projection)
13
+
14
+ apply_joins_on_records(projection, simplified_projection, records)
15
+ end
16
+
17
+ def aggregate(caller, filter, aggregation, limit = nil)
18
+ refined_filter = refine_filter(caller, filter)
19
+ replaced = {}
20
+ refined_aggregation = aggregation.replace_fields do |field_name|
21
+ if useless_join?(field_name.split(':')[0], aggregation.projection)
22
+ new_field_name = get_foreign_key_for_projection(field_name)
23
+ replaced[new_field_name] = field_name
24
+
25
+ new_field_name
26
+ else
27
+ field_name
28
+ end
29
+ end
30
+
31
+ results = child_collection.aggregate(caller, refined_filter, refined_aggregation, limit)
32
+
33
+ apply_joins_on_aggregate_result(aggregation, refined_aggregation, results, replaced)
34
+ end
35
+
36
+ def refine_filter(_caller, filter = nil)
37
+ filter&.override(
38
+ condition_tree: filter.condition_tree&.replace_leafs do |leaf|
39
+ if useless_join?(leaf.field.split(':')[0], filter.condition_tree.projection)
40
+ leaf.override(field: get_foreign_key_for_projection(leaf.field))
41
+ else
42
+ leaf
43
+ end
44
+ end
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def get_foreign_key_for_projection(field_name)
51
+ relation_name = field_name.split(':')[0]
52
+ relation_schema = schema[:fields][relation_name]
53
+
54
+ relation_schema.foreign_key
55
+ end
56
+
57
+ def useless_join?(relation_name, projection)
58
+ relation_schema = schema[:fields][relation_name]
59
+ sub_projection = projection.relations[relation_name]
60
+
61
+ relation_schema.type == 'ManyToOne' &&
62
+ sub_projection.size == 1 &&
63
+ sub_projection[0] == relation_schema.foreign_key_target
64
+ end
65
+
66
+ def get_projection_without_useless_joins(projection)
67
+ new_projection = Projection.new(projection)
68
+
69
+ projection.relations.each do |relation_name, relation_projection|
70
+ next unless useless_join?(relation_name, projection)
71
+
72
+ # remove foreign key target from projection
73
+ new_projection.delete("#{relation_name}:#{relation_projection[0]}")
74
+
75
+ # add foreign keys to projection
76
+ fk_field = get_foreign_key_for_projection("#{relation_name}:#{relation_projection[0]}")
77
+ new_projection << fk_field
78
+ end
79
+
80
+ new_projection
81
+ end
82
+
83
+ def apply_joins_on_records(initial_projection, requested_projection, records)
84
+ return records if initial_projection == requested_projection
85
+
86
+ projections_to_add = Projection.new(initial_projection.reject do |field|
87
+ requested_projection.include?(field)
88
+ end)
89
+ projections_to_rm = Projection.new(requested_projection.reject { |field| initial_projection.include?(field) })
90
+
91
+ records.each do |record|
92
+ # add to record relation:id
93
+ projections_to_add.relations.each do |relation_name, relation_projection|
94
+ relation_schema = schema[:fields][relation_name]
95
+
96
+ if relation_schema && relation_schema.type == 'ManyToOne'
97
+ fk_value = record[get_foreign_key_for_projection("#{relation_name}:#{relation_projection[0]}")]
98
+ record[relation_name] = fk_value.nil? ? nil : { relation_projection[0] => fk_value }
99
+ end
100
+ end
101
+
102
+ # remove foreign keys
103
+ projections_to_rm.each { |field| record.delete(field) }
104
+ end
105
+
106
+ records
107
+ end
108
+
109
+ def apply_joins_on_aggregate_result(initial_aggregation, requested_aggregation, results, fields_to_replace)
110
+ return result if initial_aggregation == requested_aggregation
111
+
112
+ results.each do |result|
113
+ group = {}
114
+ result['group'].each do |field, value|
115
+ if fields_to_replace.include?(field)
116
+ group[fields_to_replace[field]] = value
117
+ else
118
+ group[field] = value
119
+ end
120
+ end
121
+ result['group'] = group
122
+ end
123
+
124
+ results
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -8,6 +8,11 @@ module ForestAdminDatasourceCustomizer
8
8
  def initialize(child_collection, datasource)
9
9
  super
10
10
  @replacer = nil
11
+ @disabled_search = get_fields(false).empty?
12
+ end
13
+
14
+ def disable_search
15
+ @disabled_search = true
11
16
  end
12
17
 
13
18
  def replace_search(replacer)
@@ -15,7 +20,7 @@ module ForestAdminDatasourceCustomizer
15
20
  end
16
21
 
17
22
  def refine_schema(sub_schema)
18
- sub_schema.merge({ searchable: true })
23
+ sub_schema.merge({ searchable: !@disabled_search })
19
24
  end
20
25
 
21
26
  def refine_filter(caller, filter)
@@ -48,7 +53,7 @@ module ForestAdminDatasourceCustomizer
48
53
  private
49
54
 
50
55
  def default_replacer(search, extended)
51
- searchable_fields = get_fields(@child_collection, extended)
56
+ searchable_fields = get_fields(extended)
52
57
 
53
58
  conditions = searchable_fields.map do |field, schema|
54
59
  build_condition(field, schema, search)
@@ -64,11 +69,11 @@ module ForestAdminDatasourceCustomizer
64
69
  is_number = number?(search_string)
65
70
  is_uuid = uuid?(search_string)
66
71
 
67
- if column_type == PrimitiveType::NUMBER && is_number && filter_operators&.include?(Operators::EQUAL)
72
+ if column_type == PrimitiveType::NUMBER && is_number
68
73
  return Nodes::ConditionTreeLeaf.new(field, Operators::EQUAL, search_string.to_f)
69
74
  end
70
75
 
71
- if column_type == PrimitiveType::ENUM && filter_operators&.include?(Operators::EQUAL)
76
+ if column_type == PrimitiveType::ENUM
72
77
  search_value = lenient_find(enum_values, search_string)
73
78
 
74
79
  return Nodes::ConditionTreeLeaf.new(field, Operators::EQUAL, search_value) if search_value
@@ -92,17 +97,17 @@ module ForestAdminDatasourceCustomizer
92
97
  return Nodes::ConditionTreeLeaf.new(field, operator, search_string) if operator
93
98
  end
94
99
 
95
- if column_type == PrimitiveType::UUID && is_uuid && filter_operators&.include?(Operators::EQUAL)
100
+ if column_type == PrimitiveType::UUID && is_uuid
96
101
  return Nodes::ConditionTreeLeaf.new(field, Operators::EQUAL, search_string)
97
102
  end
98
103
 
99
104
  nil
100
105
  end
101
106
 
102
- def get_fields(collection, extended)
107
+ def get_fields(extended)
103
108
  fields = []
104
- collection.schema[:fields].each do |name, field|
105
- fields.push([name, field]) if field.type == 'Column'
109
+ @child_collection.schema[:fields].each do |name, field|
110
+ fields.push([name, field]) if field.type == 'Column' && searchable_field?(field)
106
111
 
107
112
  if field.type == 'PolymorphicManyToOne' && extended
108
113
  ForestAdminAgent::Facades::Container.logger.log(
@@ -116,16 +121,30 @@ module ForestAdminDatasourceCustomizer
116
121
  next unless extended &&
117
122
  (field.type == 'ManyToOne' || field.type == 'OneToOne' || field.type == 'PolymorphicOneToOne')
118
123
 
119
- related = collection.datasource.get_collection(field.foreign_collection)
124
+ related = @child_collection.datasource.get_collection(field.foreign_collection)
120
125
 
121
126
  related.schema[:fields].each do |sub_name, sub_field|
122
- fields.push(["#{name}:#{sub_name}", sub_field]) if sub_field.type == 'Column'
127
+ fields.push(["#{name}:#{sub_name}", sub_field]) if sub_field.type == 'Column' &&
128
+ searchable_field?(sub_field)
123
129
  end
124
130
  end
125
131
 
126
132
  fields
127
133
  end
128
134
 
135
+ def searchable_field?(field)
136
+ operators = field.filter_operators
137
+
138
+ if field.column_type == PrimitiveType::STRING
139
+ return operators&.include?(Operators::EQUAL) ||
140
+ operators&.include?(Operators::CONTAINS) ||
141
+ operators&.include?(Operators::I_CONTAINS)
142
+ end
143
+
144
+ [PrimitiveType::UUID, PrimitiveType::ENUM, PrimitiveType::NUMBER].include?(field.column_type) &&
145
+ operators&.include?(Operators::EQUAL)
146
+ end
147
+
129
148
  def lenient_find(haystack, needle)
130
149
  haystack&.find { |v| v == needle.strip } || haystack&.find { |v| v.downcase == needle.downcase.strip }
131
150
  end
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceCustomizer
2
- VERSION = "1.0.0-beta.86"
2
+ VERSION = "1.0.0-beta.88"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_customizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.86
4
+ version: 1.0.0.pre.beta.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2025-01-06 00:00:00.000000000 Z
12
+ date: 2025-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -116,6 +116,7 @@ files:
116
116
  - lib/forest_admin_datasource_customizer/decorators/hook/context/hook_context.rb
117
117
  - lib/forest_admin_datasource_customizer/decorators/hook/hook_collection_decorator.rb
118
118
  - lib/forest_admin_datasource_customizer/decorators/hook/hooks.rb
119
+ - lib/forest_admin_datasource_customizer/decorators/lazy_join/lazy_join_collection_decorator.rb
119
120
  - lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb
120
121
  - lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb
121
122
  - lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb