forest_admin_datasource_toolkit 1.0.0.pre.beta.22 → 1.0.0.pre.beta.24

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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/lib/forest_admin_datasource_toolkit/collection.rb +14 -6
  3. data/lib/forest_admin_datasource_toolkit/components/charts/chart.rb +11 -0
  4. data/lib/forest_admin_datasource_toolkit/components/charts/leaderboard_chart.rb +24 -0
  5. data/lib/forest_admin_datasource_toolkit/components/charts/line_chart.rb +24 -0
  6. data/lib/forest_admin_datasource_toolkit/components/charts/objective_chart.rb +22 -0
  7. data/lib/forest_admin_datasource_toolkit/components/charts/percentage_chart.rb +18 -0
  8. data/lib/forest_admin_datasource_toolkit/components/charts/pie_chart.rb +24 -0
  9. data/lib/forest_admin_datasource_toolkit/components/charts/smart_chart.rb +18 -0
  10. data/lib/forest_admin_datasource_toolkit/components/charts/value_chart.rb +19 -0
  11. data/lib/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rb +1 -1
  12. data/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +168 -0
  13. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb +11 -11
  14. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +1 -1
  15. data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb +3 -3
  16. data/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb +6 -6
  17. data/lib/forest_admin_datasource_toolkit/components/query/projection.rb +2 -2
  18. data/lib/forest_admin_datasource_toolkit/components/query/projection_factory.rb +3 -3
  19. data/lib/forest_admin_datasource_toolkit/datasource.rb +1 -1
  20. data/lib/forest_admin_datasource_toolkit/decorators/collection_decorator.rb +110 -0
  21. data/lib/forest_admin_datasource_toolkit/decorators/datasource_decorator.rb +28 -0
  22. data/lib/forest_admin_datasource_toolkit/utils/collection.rb +31 -14
  23. data/lib/forest_admin_datasource_toolkit/utils/schema.rb +7 -7
  24. data/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb +15 -0
  25. data/lib/forest_admin_datasource_toolkit/version.rb +1 -1
  26. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcd6677ce3f931b19f6b5e575c3235c724e8d23022ad43b768c11429cb59f537
4
- data.tar.gz: 89acacaa778c611cc3bccda72f2757bd738bee2a446381bdc6c33e7e037135f7
3
+ metadata.gz: a6db15a9f9827fdef57eb3a018ec0c3a9851a5ab131f26b38d2792fc12cb897d
4
+ data.tar.gz: 83ed4a24c607297af411ec2bbeacf739bb2c9f5c6b611343885a7237abb1deb6
5
5
  SHA512:
6
- metadata.gz: d8453ea97a82fd1c8b329a48c102cd355eaf34488103e60baa4682dce03a648f0a8aea55497ec3b3484acfc1b7ce66c20e9f06d7bcc12dd9f614c03742ff136c
7
- data.tar.gz: 374f7e949ad419ab3fb9f2928031e0c9257f8a8d1258652b829c36acadeba5da8cc81e2e6d2e6f2430ffe221550999d2e2a67f8bff460adcd08ae43d826b0342
6
+ metadata.gz: 2169823cdaa62b57be4fbef2cbd5986c4576d43ea063425ab741a9abd58af0b83b17881cad854f1e2ec6a3fe8de1006e589842fa6cfe052394252748e4d4ad53
7
+ data.tar.gz: fbc3374aefaeda605d14e882e9a988142d9a5e43c3d147546bbd1cfa17c6978429f953367e9588c02d47684146c41a32f664887d4d30205cbc228698654f3d0b
@@ -1,6 +1,6 @@
1
1
  module ForestAdminDatasourceToolkit
2
2
  class Collection < Components::Contracts::CollectionContract
3
- attr_accessor :fields, :segments
3
+ attr_accessor :segments
4
4
 
5
5
  attr_reader :actions,
6
6
  :charts,
@@ -17,13 +17,17 @@ module ForestAdminDatasourceToolkit
17
17
  @datasource = datasource
18
18
  @name = name
19
19
  @native_driver = native_driver
20
- @fields = {}
21
- @schema = {}
22
- @fields = {}
20
+ @schema = {
21
+ fields: {}
22
+ }
23
23
  @actions = {}
24
24
  @segments = {}
25
25
  @charts = {}
26
26
  @searchable = false
27
+ @countable = false
28
+ end
29
+
30
+ def enable_count
27
31
  @countable = true
28
32
  end
29
33
 
@@ -35,10 +39,14 @@ module ForestAdminDatasourceToolkit
35
39
  @searchable
36
40
  end
37
41
 
42
+ def fields
43
+ @schema[:fields]
44
+ end
45
+
38
46
  def add_field(name, field)
39
- raise Exceptions::ForestException, "Field #{name} already defined in collection" if @fields.key?(name)
47
+ raise Exceptions::ForestException, "Field #{name} already defined in collection" if @schema[:fields].key?(name)
40
48
 
41
- @fields[name] = field
49
+ @schema[:fields][name] = field
42
50
  end
43
51
 
44
52
  def add_fields(fields)
@@ -0,0 +1,11 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class Chart
5
+ def serialize
6
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class LeaderboardChart
5
+ include ForestAdminDatasourceToolkit::Validations
6
+
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ super()
11
+ @data = data
12
+ end
13
+
14
+ def serialize
15
+ data.each do |item|
16
+ ChartValidator.validate?(!item.key?(:key) || !item.key?(:value), item, "'key', 'value'")
17
+ end
18
+
19
+ data
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class LineChart
5
+ include ForestAdminDatasourceToolkit::Validations
6
+
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ super()
11
+ @data = data
12
+ end
13
+
14
+ def serialize
15
+ data.each do |item|
16
+ ChartValidator.validate?(!item.key?(:label) || !item.key?(:values), item, "'label', 'values'")
17
+ end
18
+
19
+ data
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class ObjectiveChart
5
+ attr_reader :value, :objective
6
+
7
+ def initialize(value, objective = nil)
8
+ super()
9
+ @value = value
10
+ @objective = objective
11
+ end
12
+
13
+ def serialize
14
+ result = { value: value }
15
+ result[:objective] = objective if objective
16
+
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class PercentageChart
5
+ attr_reader :value
6
+
7
+ def initialize(value)
8
+ super()
9
+ @value = value
10
+ end
11
+
12
+ def serialize
13
+ value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class PieChart
5
+ include ForestAdminDatasourceToolkit::Validations
6
+
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ super()
11
+ @data = data
12
+ end
13
+
14
+ def serialize
15
+ data.each do |item|
16
+ ChartValidator.validate?(!item.key?(:key) || !item.key?(:value), item, "'key', 'value'")
17
+ end
18
+
19
+ data
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class SmartChart
5
+ attr_reader :data
6
+
7
+ def initialize(data)
8
+ super()
9
+ @data = data
10
+ end
11
+
12
+ def serialize
13
+ data
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Components
3
+ module Charts
4
+ class ValueChart
5
+ attr_reader :value, :previous_value
6
+
7
+ def initialize(value, previous_value = nil)
8
+ super()
9
+ @value = value
10
+ @previous_value = previous_value
11
+ end
12
+
13
+ def serialize
14
+ { countCurrent: value, countPrevious: previous_value }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -10,7 +10,7 @@ module ForestAdminDatasourceToolkit
10
10
  raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
11
11
  end
12
12
 
13
- def collection(name)
13
+ def get_collection(name)
14
14
  raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
15
15
  end
16
16
 
@@ -1,3 +1,6 @@
1
+ require 'active_support/all'
2
+ require 'active_support/core_ext/numeric/time'
3
+
1
4
  module ForestAdminDatasourceToolkit
2
5
  module Components
3
6
  module Query
@@ -17,6 +20,171 @@ module ForestAdminDatasourceToolkit
17
20
 
18
21
  raise ForestException, "Aggregate operation #{operation} not allowed"
19
22
  end
23
+
24
+ def projection
25
+ aggregate_fields = []
26
+ aggregate_fields << field if field
27
+
28
+ groups.each do |group|
29
+ aggregate_fields << group[:field]
30
+ end
31
+
32
+ Projection.new(aggregate_fields)
33
+ end
34
+
35
+ def replace_fields(handler)
36
+ result = clone
37
+ result.field = handler.call(result.field) if result.field
38
+ result.groups = result.groups.map do |group|
39
+ {
40
+ field: handler.call(group[:field]),
41
+ operation: group[:operation] || nil
42
+ }
43
+ end
44
+ result
45
+ end
46
+
47
+ def override(**args)
48
+ Aggregation.new(**to_h.merge(args))
49
+ end
50
+
51
+ def apply(records, timezone, limit = nil)
52
+ rows = format_summaries(create_summaries(records, timezone))
53
+ rows.sort do |r1, r2|
54
+ if r1[:value] == r2[:value]
55
+ 0
56
+ else
57
+ r1[:value] < r2[:value] ? 1 : -1
58
+ end
59
+ end
60
+
61
+ rows = rows[0..limit - 1] if limit && rows.size > limit
62
+
63
+ rows
64
+ end
65
+
66
+ def nest(prefix = nil)
67
+ return self unless prefix
68
+
69
+ nested_field = nil
70
+ nested_groups = []
71
+ nested_field = "#{prefix}:#{field}" if field
72
+
73
+ if groups.size.positive?
74
+ nested_groups = groups.map do |item|
75
+ {
76
+ field: "#{prefix}:#{item[:field]}",
77
+ operation: item[:operation]
78
+ }
79
+ end
80
+ end
81
+
82
+ self.class.new(operation: operation, field: nested_field, groups: nested_groups)
83
+ end
84
+
85
+ def to_h
86
+ {
87
+ operation: operation,
88
+ field: field,
89
+ groups: groups
90
+ }
91
+ end
92
+
93
+ private
94
+
95
+ def create_summaries(records, timezone)
96
+ grouping_map = {}
97
+
98
+ records.each do |record|
99
+ group = create_group(record, timezone)
100
+ unique_key = Digest::SHA1.hexdigest(group.to_json)
101
+ summary = grouping_map[unique_key] || create_summary(group)
102
+
103
+ update_summary_in_place(summary, record)
104
+
105
+ grouping_map[unique_key] = summary
106
+ end
107
+
108
+ grouping_map.values
109
+ end
110
+
111
+ def format_summaries(summaries)
112
+ if operation == 'Avg'
113
+ summaries
114
+ .select { |summary| (summary['Count']).positive? }
115
+ .map do |summary|
116
+ {
117
+ group: summary['group'],
118
+ value: summary['Sum'] / summary['Count']
119
+ }
120
+ end
121
+ else
122
+ summaries.map do |summary|
123
+ {
124
+ group: summary['group'],
125
+ value: operation == 'Count' && !field ? summary['starCount'] : summary[operation]
126
+ }
127
+ end
128
+ end
129
+ end
130
+
131
+ def create_group(record, timezone)
132
+ group = {}
133
+
134
+ groups.each do |value|
135
+ group_value = record[value[:field]]
136
+ group[value[:field]] = apply_date_operation(group_value, value[:operation], timezone)
137
+ end
138
+
139
+ group
140
+ end
141
+
142
+ def apply_date_operation(value, operation, timezone)
143
+ return value unless operation
144
+
145
+ case operation
146
+ when 'Year'
147
+ DateTime.parse(value).in_time_zone(timezone).strftime('%Y-01-01')
148
+ when 'Month'
149
+ DateTime.parse(value).in_time_zone(timezone).strftime('%Y-%m-01')
150
+ when 'Day'
151
+ DateTime.parse(value).in_time_zone(timezone).strftime('%Y-%m-%d')
152
+ when 'Week'
153
+ DateTime.parse(value).in_time_zone(timezone).beginning_of_month.strftime('%Y-%m-%d')
154
+ else
155
+ value
156
+ end
157
+ end
158
+
159
+ def create_summary(group)
160
+ {
161
+ 'group' => group,
162
+ 'starCount' => 0,
163
+ 'Count' => 0,
164
+ 'Sum' => 0,
165
+ 'Min' => nil,
166
+ 'Max' => nil
167
+ }
168
+ end
169
+
170
+ def update_summary_in_place(summary, record)
171
+ summary['starCount'] += 1
172
+
173
+ return unless field
174
+
175
+ value = ForestAdminDatasourceToolkit::Utils::Record.field_value(record, field)
176
+
177
+ if value
178
+ min = summary['Min']
179
+ max = summary['Max']
180
+
181
+ summary['Count'] += 1
182
+ summary['Min'] = value if min.nil? || value < min
183
+ summary['Max'] = value if max.nil? || value < max
184
+ end
185
+
186
+ summary['Sum'] += value if value.is_a?(Numeric)
187
+ end
20
188
  end
21
189
  end
22
190
  end
@@ -41,18 +41,18 @@ module ForestAdminDatasourceToolkit
41
41
  depends_on = alt[:depends_on]
42
42
  valid = alt[:for_types].nil? || alt[:for_types].include?(column_type)
43
43
 
44
- if valid && !visited.include?(alt)
45
- depends_replacers = depends_on.map do |replacement|
46
- get_replacer(replacement, filter_operators, column_type, visited + [alt])
47
- end
44
+ next unless valid && !visited.include?(alt)
48
45
 
49
- if depends_replacers.all? { |r| !r.nil? }
50
- return lambda { |leaf, timezone|
51
- replacer.call(leaf).replace_leafs do |sub_leaf|
52
- depends_replacers[depends_on.index(sub_leaf.operator)].call(sub_leaf, timezone)
53
- end
54
- }
55
- end
46
+ depends_replacers = depends_on.map do |replacement|
47
+ get_replacer(replacement, filter_operators, column_type, visited + [alt])
48
+ end
49
+
50
+ if depends_replacers.all? { |r| !r.nil? }
51
+ return lambda { |leaf, timezone|
52
+ replacer.call(leaf).replace_leafs do |sub_leaf|
53
+ depends_replacers[depends_on.index(sub_leaf.operator)].call(sub_leaf, timezone)
54
+ end
55
+ }
56
56
  end
57
57
  end
58
58
 
@@ -22,7 +22,7 @@ module ForestAdminDatasourceToolkit
22
22
  raise ForestException, 'Collection must have at least one primary key' if primary_key_names.empty?
23
23
 
24
24
  primary_key_names.each do |name|
25
- operators = collection.fields[name].filter_operators
25
+ operators = collection.schema[:fields][name].filter_operators
26
26
  unless operators.include?(Operators::EQUAL) || operators.include?(Operators::IN)
27
27
  raise ForestException, "Field '#{name}' must support operators: ['Equal', 'In']"
28
28
  end
@@ -54,15 +54,15 @@ module ForestAdminDatasourceToolkit
54
54
  def self.previous_interval(duration)
55
55
  interval(
56
56
  lambda { |now|
57
- duration == 'quarter' ? now.prev_quarter : (now - 1.send(duration)).send("beginning_of_#{duration}")
57
+ duration == 'quarter' ? now.prev_quarter : (now - 1.send(duration)).send(:"beginning_of_#{duration}")
58
58
  },
59
- ->(now) { now.send("beginning_of_#{duration}") }
59
+ ->(now) { now.send(:"beginning_of_#{duration}") }
60
60
  )
61
61
  end
62
62
 
63
63
  def self.previous_interval_to_date(duration)
64
64
  interval(
65
- ->(now) { now.send("beginning_of_#{duration}") },
65
+ ->(now) { now.send(:"beginning_of_#{duration}") },
66
66
  ->(now) { now }
67
67
  )
68
68
  end
@@ -61,7 +61,7 @@ module ForestAdminDatasourceToolkit
61
61
  if relation.is_a?(OneToManySchema)
62
62
  origin_tree = Nodes::ConditionTreeLeaf.new(relation.origin_key, Operators::EQUAL, origin_value)
63
63
  else
64
- through_collection = collection.datasource.collection(relation.through_collection)
64
+ through_collection = collection.datasource.get_collection(relation.through_collection)
65
65
  through_tree = ConditionTreeFactory.intersect([
66
66
  Nodes::ConditionTreeLeaf.new(relation.origin_key, Operators::EQUAL, origin_value),
67
67
  Nodes::ConditionTreeLeaf.new(relation.foreign_key, Operators::PRESENT)
@@ -86,8 +86,8 @@ module ForestAdminDatasourceToolkit
86
86
  unit = unit.downcase
87
87
  start = "beginning_of_#{unit}"
88
88
  end_ = "end_of_#{unit}"
89
- start_period = Time.now.in_time_zone(timezone).send("prev_#{unit}").send(start)
90
- end_period = Time.now.in_time_zone(timezone).send("prev_#{unit}").send(end_)
89
+ start_period = Time.now.in_time_zone(timezone).send(:"prev_#{unit}").send(start)
90
+ end_period = Time.now.in_time_zone(timezone).send(:"prev_#{unit}").send(end_)
91
91
 
92
92
  get_previous_condition_tree(field, start_period.to_datetime, end_period.to_datetime)
93
93
  end
@@ -104,13 +104,13 @@ module ForestAdminDatasourceToolkit
104
104
  end
105
105
 
106
106
  def self.make_through_filter(collection, id, relation_name, caller, base_foreign_filter)
107
- relation = collection.fields[relation_name]
107
+ relation = collection.schema[:fields][relation_name]
108
108
  origin_value = Utils::Collection.get_value(collection, caller, id, relation.origin_key_target)
109
109
  foreign_relation = Utils::Collection.get_through_target(collection, relation_name)
110
110
 
111
111
  # Optimization for many to many when there is not search/segment (saves one query)
112
112
  if foreign_relation && base_foreign_filter.nestable?
113
- foreign_key = collection.datasource.collection(relation.through_collection).fields[relation.foreign_key]
113
+ foreign_key = collection.datasource.get_collection(relation.through_collection).schema[:fields][relation.foreign_key]
114
114
  base_through_filter = base_foreign_filter.nest(foreign_relation)
115
115
  condition_tree = ConditionTreeFactory.intersect(
116
116
  [
@@ -129,7 +129,7 @@ module ForestAdminDatasourceToolkit
129
129
 
130
130
  # Otherwise we have no choice but to call the target collection so that search and segment
131
131
  # are correctly apply, and then match ids in the though collection.
132
- target = collection.datasource.collection(relation.foreign_collection)
132
+ target = collection.datasource.get_collection(relation.foreign_collection)
133
133
  records = target.list(
134
134
  caller,
135
135
  make_foreign_filter(collection, id, relation_name, caller, base_foreign_filter),
@@ -9,8 +9,8 @@ module ForestAdminDatasourceToolkit
9
9
  end
10
10
 
11
11
  relations.each do |relation, projection|
12
- schema = collection.fields[relation]
13
- association = collection.datasource.collection(schema.foreign_collection)
12
+ schema = collection.schema[:fields][relation]
13
+ association = collection.datasource.get_collection(schema.foreign_collection)
14
14
  projection_with_pks = projection.with_pks(association).nest(prefix: relation)
15
15
 
16
16
  projection_with_pks.each { |field| push(field) unless include?(field) }
@@ -4,14 +4,14 @@ module ForestAdminDatasourceToolkit
4
4
  class ProjectionFactory
5
5
  include ForestAdminDatasourceToolkit::Utils
6
6
  def self.all(collection)
7
- projection_fields = collection.fields.reduce([]) do |memo, path|
7
+ projection_fields = collection.schema[:fields].reduce([]) do |memo, path|
8
8
  column_name = path[0]
9
9
  schema = path[1]
10
10
  memo += [column_name] if schema.type == 'Column'
11
11
 
12
12
  if schema.type == 'OneToOne' || schema.type == 'ManyToOne'
13
- relation = collection.datasource.collection(schema.foreign_collection)
14
- relation_columns = relation.fields
13
+ relation = collection.datasource.get_collection(schema.foreign_collection)
14
+ relation_columns = relation.schema[:fields]
15
15
  .select { |_column_name, relation_column| relation_column.type == 'Column' }
16
16
  .keys
17
17
  .map { |relation_column_name| "#{column_name}:#{relation_column_name}" }
@@ -8,7 +8,7 @@ module ForestAdminDatasourceToolkit
8
8
  @collections = {}
9
9
  end
10
10
 
11
- def collection(name)
11
+ def get_collection(name)
12
12
  raise Exceptions::ForestException, "Collection #{name} not found." unless @collections.key? name
13
13
 
14
14
  @collections[name]
@@ -0,0 +1,110 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Decorators
3
+ class CollectionDecorator < Collection
4
+ attr_reader :datasource, :child_collection, :last_schema
5
+
6
+ def initialize(child_collection, datasource)
7
+ super
8
+ @child_collection = child_collection
9
+ @datasource = datasource
10
+
11
+ # When the child collection invalidates its schema, we also invalidate ours.
12
+ # This is done like this, and not in the markSchemaAsDirty method, because we don't have
13
+ # a reference to parent collections from children.
14
+ return unless child_collection.is_a?(CollectionDecorator)
15
+
16
+ original_child_mark_schema_as_dirty = child_collection.mark_schema_as_dirty
17
+ child_collection.mark_schema_as_dirty = lambda {
18
+ # Call the original method (the child)
19
+ original_child_mark_schema_as_dirty.call(child_collection)
20
+
21
+ # Invalidate our schema (the parent)
22
+ mark_schema_as_dirty
23
+ }
24
+ end
25
+
26
+ def native_driver
27
+ # TODO
28
+ end
29
+
30
+ def schema
31
+ unless @last_schema
32
+ sub_schema = @child_collection.schema
33
+ @last_schema = refine_schema(sub_schema)
34
+ end
35
+
36
+ @last_schema
37
+ end
38
+
39
+ def name
40
+ @child_collection.name
41
+ end
42
+
43
+ def execute(caller, name, data, filter = nil)
44
+ refined_filter = refine_filter(caller, filter)
45
+
46
+ @child_collection.execute(caller, name, data, refined_filter)
47
+ end
48
+
49
+ def get_form(caller, name, data = nil, filter = nil, metas = nil)
50
+ refined_filter = refine_filter(caller, filter)
51
+
52
+ @child_collection.get_form(caller, name, data, refined_filter, metas)
53
+ end
54
+
55
+ def create(caller, data)
56
+ @child_collection.create(caller, data)
57
+ end
58
+
59
+ def list(caller, filter = nil, projection = nil)
60
+ refined_filter = refine_filter(caller, filter)
61
+
62
+ @child_collection.list(caller, refined_filter, projection)
63
+ end
64
+
65
+ def update(caller, filter, patch)
66
+ refined_filter = refine_filter(caller, filter)
67
+
68
+ @child_collection.update(caller, refined_filter, patch)
69
+ end
70
+
71
+ def delete(caller, filter)
72
+ refined_filter = refine_filter(caller, filter)
73
+
74
+ @child_collection.delete(caller, refined_filter)
75
+ end
76
+
77
+ def aggregate(caller, filter, aggregation, limit = nil)
78
+ refined_filter = refine_filter(caller, filter)
79
+
80
+ @child_collection.aggregate(caller, refined_filter, aggregation, limit)
81
+ end
82
+
83
+ def render_chart(caller, name, record_id)
84
+ @child_collection.render_chart(caller, name, record_id)
85
+ end
86
+
87
+ protected
88
+
89
+ def mark_schema_as_dirty
90
+ @last_schema = nil
91
+ end
92
+
93
+ def refine_filter(_caller, filter = nil)
94
+ filter
95
+ end
96
+
97
+ def refine_schema(sub_schema)
98
+ sub_schema
99
+ end
100
+
101
+ private
102
+
103
+ def push_customization(customization)
104
+ @stack.queue_customization(customization)
105
+
106
+ self
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,28 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Decorators
3
+ class DatasourceDecorator
4
+ def initialize(child_datasource, collection_decorator_class)
5
+ @child_datasource = child_datasource
6
+ @collection_decorator_class = collection_decorator_class
7
+ @decorators = {}
8
+ end
9
+
10
+ def collections
11
+ @child_datasource.collections.transform_values { |c| get_collection(c.name) }
12
+ end
13
+
14
+ def get_collection(name)
15
+ collection = @child_datasource.get_collection(name)
16
+ unless @decorators.key?(collection.name)
17
+ @decorators[collection.name] = @collection_decorator_class.new(collection, self)
18
+ end
19
+
20
+ @decorators[collection.name]
21
+ end
22
+
23
+ def render_chart(caller, name)
24
+ @child_datasource.render_chart(caller, name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -7,10 +7,10 @@ module ForestAdminDatasourceToolkit
7
7
  include ForestAdminDatasourceToolkit::Exceptions
8
8
 
9
9
  def self.get_inverse_relation(collection, relation_name)
10
- relation_field = collection.fields[relation_name]
11
- foreign_collection = collection.datasource.collection(relation_field.foreign_collection)
10
+ relation_field = collection.schema[:fields][relation_name]
11
+ foreign_collection = collection.datasource.get_collection(relation_field.foreign_collection)
12
12
 
13
- inverse = foreign_collection.fields.select do |_name, field|
13
+ inverse = foreign_collection.schema[:fields].select do |_name, field|
14
14
  field.is_a?(RelationSchema) &&
15
15
  field.foreign_collection == collection.name &&
16
16
  (
@@ -50,7 +50,7 @@ module ForestAdminDatasourceToolkit
50
50
  end
51
51
 
52
52
  def self.get_field_schema(collection, field_name)
53
- fields = collection.fields
53
+ fields = collection.schema[:fields]
54
54
  unless field_name.include?(':')
55
55
  raise ForestException, "Column not found #{collection.name}.#{field_name}" unless fields.key?(field_name)
56
56
 
@@ -67,7 +67,7 @@ module ForestAdminDatasourceToolkit
67
67
  end
68
68
 
69
69
  get_field_schema(
70
- collection.datasource.collection(relation_schema.foreign_collection), field_name.split(':')[1..].join(':')
70
+ collection.datasource.get_collection(relation_schema.foreign_collection), field_name.split(':')[1..].join(':')
71
71
  )
72
72
  end
73
73
 
@@ -90,11 +90,11 @@ module ForestAdminDatasourceToolkit
90
90
  end
91
91
 
92
92
  def self.get_through_target(collection, relation_name)
93
- relation = collection.fields[relation_name]
93
+ relation = collection.schema[:fields][relation_name]
94
94
  raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema)
95
95
 
96
- through_collection = collection.datasource.collection(relation.through_collection)
97
- through_collection.fields.select do |field_name, field|
96
+ through_collection = collection.datasource.get_collection(relation.through_collection)
97
+ through_collection.schema[:fields].select do |field_name, field|
98
98
  if field.is_a?(ManyToOneSchema) &&
99
99
  field.foreign_collection == relation.foreign_collection &&
100
100
  field.foreign_key == relation.foreign_key &&
@@ -106,15 +106,32 @@ module ForestAdminDatasourceToolkit
106
106
  nil
107
107
  end
108
108
 
109
+ def self.get_through_origin(collection, relation_name)
110
+ relation = collection.schema[:fields][relation_name]
111
+ raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema)
112
+
113
+ through_collection = collection.datasource.get_collection(relation.through_collection)
114
+ through_collection.schema[:fields].select do |field_name, field|
115
+ if field.is_a?(ManyToOneSchema) &&
116
+ field.foreign_collection == collection.name &&
117
+ field.foreign_key == relation.origin_key &&
118
+ field.foreign_key_target == relation.origin_key_target
119
+ return field_name
120
+ end
121
+ end
122
+
123
+ nil
124
+ end
125
+
109
126
  def self.list_relation(collection, id, relation_name, caller, foreign_filter, projection)
110
- relation = collection.fields[relation_name]
111
- foreign_collection = collection.datasource.collection(relation.foreign_collection)
127
+ relation = collection.schema[:fields][relation_name]
128
+ foreign_collection = collection.datasource.get_collection(relation.foreign_collection)
112
129
 
113
130
  if relation.is_a?(ManyToManySchema) && foreign_filter.nestable?
114
131
  foreign_relation = get_through_target(collection, relation_name)
115
132
 
116
133
  if foreign_relation
117
- through_collection = collection.datasource.collection(relation.through_collection)
134
+ through_collection = collection.datasource.get_collection(relation.through_collection)
118
135
  records = through_collection.list(
119
136
  caller,
120
137
  FilterFactory.make_through_filter(collection, id, relation_name, caller, foreign_filter),
@@ -133,13 +150,13 @@ module ForestAdminDatasourceToolkit
133
150
  end
134
151
 
135
152
  def self.aggregate_relation(collection, id, relation_name, caller, foreign_filter, aggregation, limit = nil)
136
- relation = collection.fields[relation_name]
137
- foreign_collection = collection.datasource.collection(relation.foreign_collection)
153
+ relation = collection.schema[:fields][relation_name]
154
+ foreign_collection = collection.datasource.get_collection(relation.foreign_collection)
138
155
 
139
156
  if relation.is_a?(ManyToManySchema) && foreign_filter.nestable?
140
157
  foreign_relation = get_through_target(collection, relation_name)
141
158
  if foreign_relation
142
- through_collection = collection.datasource.collection(relation.through_collection)
159
+ through_collection = collection.datasource.get_collection(relation.through_collection)
143
160
 
144
161
  return through_collection.aggregate(
145
162
  caller,
@@ -2,33 +2,33 @@ module ForestAdminDatasourceToolkit
2
2
  module Utils
3
3
  class Schema
4
4
  def self.foreign_key?(collection, name)
5
- field = collection.fields[name]
5
+ field = collection.schema[:fields][name]
6
6
 
7
7
  field.type == 'Column' &&
8
- collection.fields.any? do |_key, relation|
8
+ collection.schema[:fields].any? do |_key, relation|
9
9
  relation.type == 'ManyToOne' && relation.foreign_key == name
10
10
  end
11
11
  end
12
12
 
13
13
  def self.primary_key?(collection, name)
14
- field = collection.fields[name]
14
+ field = collection.schema[:fields][name]
15
15
 
16
16
  field.type == 'Column' && field.is_primary_key
17
17
  end
18
18
 
19
19
  def self.primary_keys(collection)
20
- collection.fields.keys.select do |field_name|
21
- field = collection.fields[field_name]
20
+ collection.schema[:fields].keys.select do |field_name|
21
+ field = collection.schema[:fields][field_name]
22
22
  field.type == 'Column' && field.is_primary_key
23
23
  end
24
24
  end
25
25
 
26
26
  def self.get_to_many_relation(collection, relation_name)
27
- unless collection.fields.key?(relation_name)
27
+ unless collection.schema[:fields].key?(relation_name)
28
28
  raise Exceptions::ForestException, "Relation #{relation_name} not found"
29
29
  end
30
30
 
31
- relation = collection.fields[relation_name]
31
+ relation = collection.schema[:fields][relation_name]
32
32
 
33
33
  if relation.type != 'OneToMany' && relation.type != 'ManyToMany'
34
34
  raise Exceptions::ForestException,
@@ -0,0 +1,15 @@
1
+ module ForestAdminDatasourceToolkit
2
+ module Validations
3
+ class ChartValidator
4
+ def self.validate?(condition, result, key_names)
5
+ if condition
6
+ result_keys = result.keys.join(',')
7
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
8
+ "The result columns must be named '#{key_names}' instead of '#{result_keys}'"
9
+ end
10
+
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceToolkit
2
- VERSION = "1.0.0-beta.22"
2
+ VERSION = "1.0.0-beta.24"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.22
4
+ version: 1.0.0.pre.beta.24
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: 2023-12-08 00:00:00.000000000 Z
12
+ date: 2023-12-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -56,6 +56,14 @@ files:
56
56
  - lib/forest_admin_datasource_toolkit.rb
57
57
  - lib/forest_admin_datasource_toolkit/collection.rb
58
58
  - lib/forest_admin_datasource_toolkit/components/caller.rb
59
+ - lib/forest_admin_datasource_toolkit/components/charts/chart.rb
60
+ - lib/forest_admin_datasource_toolkit/components/charts/leaderboard_chart.rb
61
+ - lib/forest_admin_datasource_toolkit/components/charts/line_chart.rb
62
+ - lib/forest_admin_datasource_toolkit/components/charts/objective_chart.rb
63
+ - lib/forest_admin_datasource_toolkit/components/charts/percentage_chart.rb
64
+ - lib/forest_admin_datasource_toolkit/components/charts/pie_chart.rb
65
+ - lib/forest_admin_datasource_toolkit/components/charts/smart_chart.rb
66
+ - lib/forest_admin_datasource_toolkit/components/charts/value_chart.rb
59
67
  - lib/forest_admin_datasource_toolkit/components/contracts/collection_contract.rb
60
68
  - lib/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rb
61
69
  - lib/forest_admin_datasource_toolkit/components/query/aggregation.rb
@@ -74,6 +82,8 @@ files:
74
82
  - lib/forest_admin_datasource_toolkit/components/query/projection.rb
75
83
  - lib/forest_admin_datasource_toolkit/components/query/projection_factory.rb
76
84
  - lib/forest_admin_datasource_toolkit/datasource.rb
85
+ - lib/forest_admin_datasource_toolkit/decorators/collection_decorator.rb
86
+ - lib/forest_admin_datasource_toolkit/decorators/datasource_decorator.rb
77
87
  - lib/forest_admin_datasource_toolkit/exceptions/forest_exception.rb
78
88
  - lib/forest_admin_datasource_toolkit/schema/column_schema.rb
79
89
  - lib/forest_admin_datasource_toolkit/schema/primitive_type.rb
@@ -85,6 +95,7 @@ files:
85
95
  - lib/forest_admin_datasource_toolkit/utils/collection.rb
86
96
  - lib/forest_admin_datasource_toolkit/utils/record.rb
87
97
  - lib/forest_admin_datasource_toolkit/utils/schema.rb
98
+ - lib/forest_admin_datasource_toolkit/validations/chart_validator.rb
88
99
  - lib/forest_admin_datasource_toolkit/version.rb
89
100
  - sig/forest_admin_datasource_toolkit.rbs
90
101
  - sig/forest_admin_datasource_toolkit/collection.rbs