forest_admin_agent 1.0.0.pre.beta.22 → 1.0.0.pre.beta.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5714273d6e7144f00eb531b7a93d0e908698bd1dac7984dc47affd15a7514f6
4
- data.tar.gz: 3b47af0daae4bc4e02b944f0d21be13aa18fe5e7abb9bfcfa03f2ea3272028bd
3
+ metadata.gz: c342682e272a5886928f4c48de0e35dbd6bcf013c4f888aa56bac238cca695da
4
+ data.tar.gz: 300d73bdac310e91c2aa4a044d095b782bd362a0fb3c6309871fad0b1fff2930
5
5
  SHA512:
6
- metadata.gz: 93044527fdfb1a06f49408044479258b1cccb6af15c8025cf26310f01c684ae6b5c4657bd25496eb69d911eaa77daae83ec82ec551469bd15cacea61a3b46687
7
- data.tar.gz: f93863c6d28342854e2e453795bca3c3892c6a09c0d91a44fcb42fae57b107c17f6daa3e9694d04e0862bb46e1f95ff2d14f04c199ab0c2a5436434ca5090ca4
6
+ metadata.gz: 50d05943fa75c8559e127bd181f91bb36dc64352237126381d4bd1fd769fa0e51389b74051e1f7cdc55f7c6747fbdb2c91259cf135c6e2e7e7665c5cef8cefcd
7
+ data.tar.gz: be53569ca0ad92511dc33b118693445e5a2a522faa98fd24e4d3662bf70eaa3ac7f9614cc1dccc6db444f8fe204d39db43eca1709f8efe4e528f7fef2b200ebf
@@ -34,6 +34,7 @@ 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 "deepsort", "~> 0.4.5"
37
38
  spec.add_dependency "dry-container", "~> 0.11"
38
39
  spec.add_dependency "faraday", "~> 2.7"
39
40
  spec.add_dependency "filecache", "~> 1.0"
@@ -63,9 +63,7 @@ module ForestAdminAgent
63
63
  return unless @has_env_secret
64
64
 
65
65
  cache = @container.resolve(:cache)
66
- cache.get_or_set 'config' do
67
- @options.to_h
68
- end
66
+ cache.set('config', @options.to_h)
69
67
  end
70
68
 
71
69
  def build_logger
@@ -9,6 +9,7 @@ module ForestAdminAgent
9
9
  # api_charts_routes,
10
10
  System::HealthCheck.new.routes,
11
11
  Security::Authentication.new.routes,
12
+ Charts::Charts.new.routes,
12
13
  Resources::Count.new.routes,
13
14
  Resources::Delete.new.routes,
14
15
  Resources::List.new.routes,
@@ -0,0 +1,214 @@
1
+ require 'jsonapi-serializers'
2
+ require 'active_support/inflector'
3
+
4
+ module ForestAdminAgent
5
+ module Routes
6
+ module Charts
7
+ class Charts < AbstractAuthenticatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminAgent::Utils
10
+ include ForestAdminDatasourceToolkit::Components::Query
11
+ include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
12
+ include ForestAdminDatasourceToolkit::Components::Charts
13
+
14
+ attr_reader :filter
15
+
16
+ FORMAT = {
17
+ Day: '%d/%m/%Y',
18
+ Week: 'W%W-%Y',
19
+ Month: '%b %y',
20
+ Year: '%Y'
21
+ }.freeze
22
+
23
+ def setup_routes
24
+ add_route('forest_chart', 'post', '/stats/:collection_name', lambda { |args|
25
+ handle_request(args)
26
+ })
27
+ self
28
+ end
29
+
30
+ def handle_request(args = {})
31
+ build(args)
32
+ @permissions.can_chart?(args[:params])
33
+ @args = args
34
+ self.type = args[:params][:type]
35
+ @filter = Filter.new(
36
+ condition_tree: ConditionTreeFactory.intersect(
37
+ [
38
+ @permissions.get_scope(@collection),
39
+ ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(
40
+ @collection, args
41
+ )
42
+ ]
43
+ )
44
+ )
45
+
46
+ inject_context_variables
47
+
48
+ { content: Serializer::ForestChartSerializer.serialize(send(:"make_#{@type}")) }
49
+ end
50
+
51
+ private
52
+
53
+ def type=(type)
54
+ chart_types = %w[Value Objective Pie Line Leaderboard]
55
+ unless chart_types.include?(type)
56
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Invalid Chart type #{type}"
57
+ end
58
+
59
+ @type = type.downcase
60
+ end
61
+
62
+ def inject_context_variables
63
+ user = @permissions.get_user_data(@caller.id)
64
+ team = @permissions.get_team(@caller.rendering_id)
65
+
66
+ context_variables = ForestAdminAgent::Utils::ContextVariables.new(team, user,
67
+ @args[:params][:contextVariables])
68
+ return unless @args[:params][:filter]
69
+
70
+ @filter = @filter.override(condition_tree: ContextVariablesInjector.inject_context_in_filter(
71
+ @filter.condition_tree, context_variables
72
+ ))
73
+ end
74
+
75
+ def make_value
76
+ value = compute_value(@filter)
77
+ previous_value = nil
78
+ is_and_aggregator = @filter.condition_tree&.try(:aggregator) == 'And'
79
+ with_count_previous = @filter.condition_tree&.some_leaf(&:use_interval_operator)
80
+
81
+ if with_count_previous && !is_and_aggregator
82
+ previous_value = compute_value(FilterFactory.get_previous_period_filter(@filter, @caller.timezone))
83
+ end
84
+
85
+ ValueChart.new(value, previous_value)
86
+ end
87
+
88
+ def make_objective
89
+ ObjectiveChart.new(compute_value(@filter))
90
+ end
91
+
92
+ def make_pie
93
+ group_field = @args[:params][:groupByFieldName]
94
+ aggregation = Aggregation.new(
95
+ operation: @args[:params][:aggregator],
96
+ field: @args[:params][:aggregateFieldName],
97
+ groups: group_field ? [{ field: group_field }] : []
98
+ )
99
+
100
+ result = @collection.aggregate(@caller, @filter, aggregation)
101
+
102
+ PieChart.new(result.map { |row| { key: row[:group][group_field], value: row[:value] } })
103
+ end
104
+
105
+ def make_line
106
+ group_by_field_name = @args[:params][:groupByFieldName]
107
+ time_range = @args[:params][:timeRange]
108
+ filter_only_with_values = @filter.override(
109
+ condition_tree: ConditionTree::ConditionTreeFactory.intersect(
110
+ [
111
+ @filter.condition_tree,
112
+ ConditionTree::Nodes::ConditionTreeLeaf.new(group_by_field_name, ConditionTree::Operators::PRESENT)
113
+ ]
114
+ )
115
+ )
116
+ rows = @collection.aggregate(
117
+ @caller,
118
+ filter_only_with_values,
119
+ Aggregation.new(
120
+ operation: @args[:params][:aggregator],
121
+ field: @args[:params][:aggregateField],
122
+ groups: [{ field: group_by_field_name, operation: time_range }]
123
+ )
124
+ )
125
+
126
+ values = {}
127
+ rows.each { |row| values[row[:group][group_by_field_name]] = row[:value] }
128
+ dates = values.keys.sort
129
+ current = dates[0]
130
+ last = dates.last
131
+ result = []
132
+ while current <= last
133
+ result << {
134
+ label: current.strftime(FORMAT[time_range.to_sym]),
135
+ values: { value: values[current] || 0 }
136
+ }
137
+ current += 1.send(time_range.downcase.pluralize.to_sym)
138
+ end
139
+
140
+ LineChart.new(result)
141
+ end
142
+
143
+ def make_leaderboard
144
+ field = @collection.fields[@args[:params][:relationshipFieldName]]
145
+
146
+ if field && field.type == 'OneToMany'
147
+ inverse = ForestAdminDatasourceToolkit::Utils::Collection.get_inverse_relation(
148
+ @collection,
149
+ @args[:params][:relationshipFieldName]
150
+ )
151
+ if inverse
152
+ collection = field.foreign_collection
153
+ filter = @filter.nest(inverse)
154
+ aggregation = Aggregation.new(
155
+ operation: @args[:params][:aggregator],
156
+ field: @args[:params][:aggregateFieldName],
157
+ groups: [{ field: "#{inverse}:#{@args[:params][:labelFieldName]}" }]
158
+ )
159
+ end
160
+ end
161
+
162
+ if field && field.type == 'ManyToMany'
163
+ origin = ForestAdminDatasourceToolkit::Utils::Collection.get_through_origin(
164
+ @collection,
165
+ @args[:params][:relationshipFieldName]
166
+ )
167
+ target = ForestAdminDatasourceToolkit::Utils::Collection.get_through_target(
168
+ @collection,
169
+ @args[:params][:relationshipFieldName]
170
+ )
171
+ if origin && target
172
+ collection = field.through_collection
173
+ filter = @filter.nest(origin)
174
+ aggregation = Aggregation.new(
175
+ operation: @args[:params][:aggregator],
176
+ field: @args[:params][:aggregateFieldName] ? "#{target}:#{@args[:params][:aggregateFieldName]}" : nil,
177
+ groups: [{ field: "#{origin}:#{@args[:params][:labelFieldName]}" }]
178
+ )
179
+ end
180
+ end
181
+
182
+ if collection && filter && aggregation
183
+ rows = @datasource.collection(collection).aggregate(
184
+ @caller,
185
+ filter,
186
+ aggregation,
187
+ @args[:params][:limit]
188
+ )
189
+
190
+ result = rows.map do |row|
191
+ {
192
+ key: row[:group][aggregation.groups[0][:field]],
193
+ value: row[:value]
194
+ }
195
+ end
196
+
197
+ return LeaderboardChart.new(result)
198
+ end
199
+
200
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
201
+ 'Failed to generate leaderboard chart: parameters do not match pre-requisites'
202
+ end
203
+
204
+ def compute_value(filter)
205
+ aggregation = Aggregation.new(operation: @args[:params][:aggregator],
206
+ field: @args[:params][:aggregateFieldName])
207
+ result = @collection.aggregate(@caller, filter, aggregation)
208
+
209
+ result[0][:value] || 0
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -33,17 +33,17 @@ module ForestAdminAgent
33
33
  def link_one_to_one_relations(args, record)
34
34
  args[:params][:data][:relationships]&.map do |field, value|
35
35
  schema = @collection.fields[field]
36
- if schema.type == 'OneToOne'
37
- id = Utils::Id.unpack_id(@collection, value['data']['id'], with_key: true)
38
- foreign_collection = @datasource.collection(schema.foreign_collection)
39
- # Load the value that will be used as origin_key
40
- origin_value = record[schema.origin_key_target]
36
+ next unless schema.type == 'OneToOne'
41
37
 
42
- # update new relation (may update zero or one records).
43
- condition_tree = ConditionTree::ConditionTreeFactory.match_records(foreign_collection, [id])
44
- filter = Filter.new(condition_tree: condition_tree)
45
- foreign_collection.update(@caller, filter, { schema.origin_key => origin_value })
46
- end
38
+ id = Utils::Id.unpack_id(@collection, value['data']['id'], with_key: true)
39
+ foreign_collection = @datasource.collection(schema.foreign_collection)
40
+ # Load the value that will be used as origin_key
41
+ origin_value = record[schema.origin_key_target]
42
+
43
+ # update new relation (may update zero or one records).
44
+ condition_tree = ConditionTree::ConditionTreeFactory.match_records(foreign_collection, [id])
45
+ filter = Filter.new(condition_tree: condition_tree)
46
+ foreign_collection.update(@caller, filter, { schema.origin_key => origin_value })
47
47
  end
48
48
  end
49
49
  end
@@ -0,0 +1,19 @@
1
+ require 'securerandom'
2
+
3
+ module ForestAdminAgent
4
+ module Serializer
5
+ class ForestChartSerializer
6
+ def self.serialize(chart)
7
+ {
8
+ data: {
9
+ id: SecureRandom.uuid,
10
+ type: 'stats',
11
+ attributes: {
12
+ value: chart.serialize
13
+ }
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,4 +1,5 @@
1
1
  require 'filecache'
2
+ require 'deepsort'
2
3
 
3
4
  module ForestAdminAgent
4
5
  module Services
@@ -56,7 +57,7 @@ module ForestAdminAgent
56
57
  end
57
58
 
58
59
  def can_chart?(parameters)
59
- attributes = sanitize_chart_parameters(parameters)
60
+ attributes = sanitize_chart_parameters(parameters.deep_symbolize_keys)
60
61
  hash_request = "#{attributes[:type]}:#{array_hash(attributes)}"
61
62
  is_allowed = get_chart_data(caller.rendering_id).include?(hash_request)
62
63
 
@@ -99,8 +100,6 @@ module ForestAdminAgent
99
100
  def get_scope(collection)
100
101
  permissions = get_scope_and_team_data(caller.rendering_id)
101
102
  scope = permissions[:scopes][collection.name.to_sym]
102
- team = permissions[:team]
103
- user = get_user_data(caller.id)
104
103
 
105
104
  return nil if scope.nil?
106
105
 
@@ -170,16 +169,21 @@ module ForestAdminAgent
170
169
  end
171
170
 
172
171
  def sanitize_chart_parameters(parameters)
173
- # parameters = parameters.to_h
174
172
  parameters.delete(:timezone)
175
173
  parameters.delete(:collection)
176
174
  parameters.delete(:contextVariables)
175
+ # rails
176
+ parameters.delete(:route_alias)
177
+ parameters.delete(:controller)
178
+ parameters.delete(:action)
179
+ parameters.delete(:collection_name)
180
+ parameters.delete(:forest)
177
181
 
178
182
  parameters.select { |_, value| !value.nil? && value != '' }
179
183
  end
180
184
 
181
185
  def array_hash(data)
182
- Digest::SHA1.hexdigest(data.sort.to_h.to_s)
186
+ Digest::SHA1.hexdigest(data.deep_sort.to_h.to_s)
183
187
  end
184
188
 
185
189
  def get_scope_and_team_data(rendering_id)
@@ -197,7 +201,7 @@ module ForestAdminAgent
197
201
  def permission_system?
198
202
  cache.get_or_set('forest.has_permission') do
199
203
  response = fetch('/liana/v4/permissions/environment')
200
- { enable: !response.nil? }
204
+ { enable: response != true }
201
205
  end[:enable]
202
206
  end
203
207
 
@@ -1,4 +1,5 @@
1
1
  require 'jwt'
2
+ require 'active_support'
2
3
  require 'active_support/time'
3
4
 
4
5
  module ForestAdminAgent
@@ -7,7 +7,7 @@ module ForestAdminAgent
7
7
  class SchemaEmitter
8
8
  LIANA_NAME = "forest-rails"
9
9
 
10
- LIANA_VERSION = "1.0.0-beta.22"
10
+ LIANA_VERSION = "1.0.0-beta.23"
11
11
 
12
12
  def self.get_serialized_schema(datasource)
13
13
  schema_path = Facades::Container.cache(:schema_path)
@@ -1,3 +1,3 @@
1
1
  module ForestAdminAgent
2
- VERSION = "1.0.0-beta.22"
2
+ VERSION = "1.0.0-beta.23"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.22
4
+ version: 1.0.0.pre.beta.23
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-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '6.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: deepsort
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.4.5
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.4.5
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: dry-container
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -229,6 +243,7 @@ files:
229
243
  - lib/forest_admin_agent/routes/abstract_authenticated_route.rb
230
244
  - lib/forest_admin_agent/routes/abstract_related_route.rb
231
245
  - lib/forest_admin_agent/routes/abstract_route.rb
246
+ - lib/forest_admin_agent/routes/charts/charts.rb
232
247
  - lib/forest_admin_agent/routes/resources/count.rb
233
248
  - lib/forest_admin_agent/routes/resources/delete.rb
234
249
  - lib/forest_admin_agent/routes/resources/list.rb
@@ -242,6 +257,7 @@ files:
242
257
  - lib/forest_admin_agent/routes/resources/update.rb
243
258
  - lib/forest_admin_agent/routes/security/authentication.rb
244
259
  - lib/forest_admin_agent/routes/system/health_check.rb
260
+ - lib/forest_admin_agent/serializer/forest_chart_serializer.rb
245
261
  - lib/forest_admin_agent/serializer/forest_serializer.rb
246
262
  - lib/forest_admin_agent/serializer/forest_serializer_override.rb
247
263
  - lib/forest_admin_agent/services/ip_whitelist.rb