simple_drilldown 0.1.1 → 0.2.0
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/lib/generators/drilldown_controller/drilldown_controller_generator.rb +3 -1
- data/lib/simple_drilldown/drilldown_controller.rb +236 -98
- data/lib/simple_drilldown/drilldown_helper.rb +1 -1
- data/lib/simple_drilldown/engine.rb +2 -2
- data/lib/simple_drilldown/search.rb +48 -33
- data/lib/simple_drilldown/version.rb +1 -1
- metadata +15 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 203d9f2001a9677cd008ef0e7f9a10755420c1a06269d3fe2e37cb7467d35dcc
|
4
|
+
data.tar.gz: 18bbbd147c2f78ae45674054c6f5af919f49e792da115971ba145693f2528f67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81dc94488937c04c7563cbc6560fed331cb8bd92a57751fc2df36372c87fe6e25d206d4a983f34d7f3473786bdb78e452a4058f453cf1fa48ec51b94d2efb3d3
|
7
|
+
data.tar.gz: f3fcaed4cadf14219ed36cc683e8ad04fc16c27b80c1b5112fb133cfb8a2060d81b8210953b656c848da2fa63c4989c75f3e02cbd91d2c940e476148d92fb2fb
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class DrilldownControllerGenerator < Rails::Generators::NamedBase
|
2
4
|
source_root File.expand_path('templates', __dir__)
|
3
5
|
|
4
6
|
def copy_drilldown_controller_file
|
5
|
-
template
|
7
|
+
template 'drilldown_controller.rb.erb', "app/controllers/#{file_name}_drilldown_controller.rb"
|
6
8
|
route "resources(:#{singular_name}_drilldown, only: :index){collection{get :excel_export;get :html_export}}"
|
7
9
|
end
|
8
10
|
end
|
@@ -47,26 +47,52 @@ module SimpleDrilldown
|
|
47
47
|
@@fields[name] = options
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
51
|
-
|
50
|
+
def summary_fields(*summary_fields)
|
51
|
+
@@summary_fields = summary_fields
|
52
|
+
end
|
53
|
+
|
54
|
+
def dimension(name, select_expression = name, options = {})
|
52
55
|
interval = options.delete(:interval)
|
53
|
-
label_method = options.delete(:label_method)
|
56
|
+
label_method = options.delete(:label_method)
|
54
57
|
legal_values = options.delete(:legal_values) || legal_values_for(name)
|
55
58
|
reverse = options.delete(:reverse)
|
59
|
+
row_class = options.delete(:row_class)
|
60
|
+
if select_expression.is_a?(Array)
|
61
|
+
queries = select_expression
|
62
|
+
else
|
63
|
+
includes = options.delete(:includes)
|
64
|
+
conditions = options.delete(:where)
|
65
|
+
queries = [{
|
66
|
+
select: select_expression,
|
67
|
+
includes: includes,
|
68
|
+
where: conditions
|
69
|
+
}]
|
70
|
+
end
|
71
|
+
raise "Unexpected options: #{options.inspect}" if options.present?
|
56
72
|
|
57
|
-
|
73
|
+
queries.each do |query_opts|
|
74
|
+
raise "Unknown options: #{query_opts.keys.inspect}" unless (query_opts.keys - %i[select includes where]).empty?
|
75
|
+
end
|
58
76
|
|
59
77
|
@@dimension_defs ||= Concurrent::Hash.new
|
60
78
|
|
61
79
|
@@dimension_defs[name.to_s] = {
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
80
|
+
includes: queries.inject([]) do |a, e|
|
81
|
+
i = e[:includes]
|
82
|
+
next a unless i
|
83
|
+
next a if a.include?(i)
|
84
|
+
|
85
|
+
a + [i]
|
86
|
+
end,
|
87
|
+
interval: interval,
|
88
|
+
label_method: label_method,
|
89
|
+
legal_values: legal_values,
|
90
|
+
pretty_name: I18n.t(name),
|
91
|
+
queries: queries,
|
92
|
+
reverse: reverse,
|
93
|
+
select_expression: queries.size > 1 ? "COALESCE(#{queries.map { |q| q[:select] }.join(',')})" : queries[0][:select],
|
94
|
+
row_class: row_class,
|
95
|
+
url_param_name: name.to_s
|
70
96
|
}
|
71
97
|
end
|
72
98
|
|
@@ -74,29 +100,38 @@ module SimpleDrilldown
|
|
74
100
|
lambda do |search|
|
75
101
|
my_filter = search.filter.dup
|
76
102
|
my_filter.delete(field.to_s) unless preserve_filter
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if
|
81
|
-
|
82
|
-
|
83
|
-
|
103
|
+
filter_conditions, _t, includes = make_conditions(my_filter)
|
104
|
+
dimension_def = @@dimension_defs[field.to_s]
|
105
|
+
result_sets = dimension_def[:queries].map do |query|
|
106
|
+
if query[:includes]
|
107
|
+
if query[:includes].is_a?(Array)
|
108
|
+
includes += query[:includes]
|
109
|
+
else
|
110
|
+
includes << query[:includes]
|
111
|
+
end
|
112
|
+
includes.uniq!
|
84
113
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
114
|
+
rows = @@target_class.unscoped.where(@@base_condition)
|
115
|
+
.select("#{query[:select]} AS value")
|
116
|
+
.where(filter_conditions)
|
117
|
+
.where(query[:where])
|
118
|
+
.joins(make_join([], @@target_class.name.underscore.to_sym, includes))
|
119
|
+
.order('value')
|
120
|
+
.group(:value)
|
121
|
+
.to_a
|
122
|
+
filter_fields = search.filter[field.to_s]
|
123
|
+
filter_fields&.each do |selected_value|
|
124
|
+
next if rows.find { |r| r[:value].to_s == selected_value }
|
125
|
+
|
126
|
+
# FIXME(uwe): Convert the selected value to same data type as legal values
|
95
127
|
rows << { value: selected_value }
|
128
|
+
# EMXIF
|
96
129
|
end
|
130
|
+
rows.map { |r| [dimension_def[:label_method]&.call(r[:value]) || r[:value], r[:value]] }
|
97
131
|
end
|
98
|
-
values =
|
99
|
-
values.
|
132
|
+
values = result_sets.inject(&:+).uniq
|
133
|
+
values.sort! if dimension_def[:queries].size > 1
|
134
|
+
values.reverse! if dimension_def[:reverse]
|
100
135
|
values
|
101
136
|
end
|
102
137
|
end
|
@@ -115,22 +150,24 @@ module SimpleDrilldown
|
|
115
150
|
values = [*values]
|
116
151
|
if dimension_def[:interval]
|
117
152
|
values *= 2 if values.size == 1
|
118
|
-
if values.size != 2
|
119
|
-
raise "Need 2 values for interval filter: #{values.inspect}"
|
120
|
-
end
|
153
|
+
raise "Need 2 values for interval filter: #{values.inspect}" if values.size != 2
|
121
154
|
|
122
|
-
if
|
155
|
+
if values[0].present? && values[1].present?
|
123
156
|
condition_strings << "#{dimension_def[:select_expression]} BETWEEN ? AND ?"
|
124
157
|
condition_values += values
|
125
|
-
filter_texts <<
|
126
|
-
|
158
|
+
filter_texts << <<~TEXT
|
159
|
+
#{dimension_def[:pretty_name]} #{dimension_def[:label_method]&.call(values) || "from #{values[0]} to #{values[1]}"}
|
160
|
+
TEXT
|
161
|
+
elsif values[0].present?
|
127
162
|
condition_strings << "#{dimension_def[:select_expression]} >= ?"
|
128
|
-
condition_values
|
129
|
-
filter_texts <<
|
130
|
-
|
163
|
+
condition_values << values[0]
|
164
|
+
filter_texts <<
|
165
|
+
"#{dimension_def[:pretty_name]} #{dimension_def[:label_method]&.call(values) || "from #{values[0]}"}"
|
166
|
+
elsif values[1].present?
|
131
167
|
condition_strings << "#{dimension_def[:select_expression]} <= ?"
|
132
|
-
condition_values
|
133
|
-
filter_texts <<
|
168
|
+
condition_values << values[1]
|
169
|
+
filter_texts <<
|
170
|
+
"#{dimension_def[:pretty_name]} #{dimension_def[:label_method]&.call(values) || "to #{values[1]}"}"
|
134
171
|
end
|
135
172
|
includes << dimension_def[:includes] if dimension_def[:includes]
|
136
173
|
else
|
@@ -140,7 +177,8 @@ module SimpleDrilldown
|
|
140
177
|
else
|
141
178
|
condition_values << value
|
142
179
|
end
|
143
|
-
filter_texts <<
|
180
|
+
filter_texts <<
|
181
|
+
"#{dimension_def[:pretty_name]} #{dimension_def[:label_method]&.call(value) || value}"
|
144
182
|
includes << dimension_def[:includes] if dimension_def[:includes]
|
145
183
|
"(#{dimension_def[:select_expression]}) = ?"
|
146
184
|
end.join(' OR ')
|
@@ -148,11 +186,12 @@ module SimpleDrilldown
|
|
148
186
|
end
|
149
187
|
filter_text = filter_texts.join(' and ')
|
150
188
|
conditions = [condition_strings.map { |c| "(#{c})" }.join(' AND '), *condition_values]
|
151
|
-
includes.uniq!
|
189
|
+
includes.keep_if(&:present?).uniq!
|
152
190
|
else
|
153
191
|
filter_text = nil
|
154
192
|
conditions = nil
|
155
193
|
end
|
194
|
+
conditions = nil if conditions == ['']
|
156
195
|
[conditions, filter_text, includes]
|
157
196
|
end
|
158
197
|
|
@@ -161,7 +200,7 @@ module SimpleDrilldown
|
|
161
200
|
when Array
|
162
201
|
include.map { |i| make_join(joins, model, i) }.join(' ')
|
163
202
|
when Hash
|
164
|
-
sql = ''
|
203
|
+
sql = +''
|
165
204
|
include.each do |parent, child|
|
166
205
|
sql << make_join(joins, model, parent) + ' '
|
167
206
|
ass = model.to_s.camelize.constantize.reflect_on_association parent
|
@@ -171,6 +210,7 @@ module SimpleDrilldown
|
|
171
210
|
when Symbol
|
172
211
|
return '' if joins.include?(include)
|
173
212
|
|
213
|
+
joins = joins.dup
|
174
214
|
joins << include
|
175
215
|
ass = model_class.reflect_on_association include
|
176
216
|
raise "Unknown association: #{model} => #{include}" unless ass
|
@@ -183,17 +223,20 @@ module SimpleDrilldown
|
|
183
223
|
"LEFT JOIN #{include_table} #{include_alias} ON #{include_alias}.id = #{model_table}.#{include}_id"
|
184
224
|
when :has_one, :has_many
|
185
225
|
fk_col = ass.options[:foreign_key] || "#{model}_id"
|
186
|
-
sql = "LEFT JOIN #{include_table} #{include_alias} ON #{include_alias}.#{fk_col} = #{model_table}.id"
|
187
|
-
|
188
|
-
|
226
|
+
sql = +"LEFT JOIN #{include_table} #{include_alias} ON #{include_alias}.#{fk_col} = #{model_table}.id"
|
227
|
+
sql << " AND #{include_alias}.deleted_at IS NULL" if ass.klass.paranoid?
|
228
|
+
if ass.scope && (ass_order = ScopeHolder.new(ass.scope).to_s)
|
229
|
+
ass_order = ass_order.sub(/ DESC\s*$/i, '')
|
189
230
|
ass_order_prefixed = ass_order.dup
|
190
231
|
ActiveRecord::Base.connection.columns(include_table).map(&:name).each do |cname|
|
191
232
|
ass_order_prefixed.gsub!(/\b#{cname}\b/, "#{include_alias}.#{cname}")
|
192
233
|
end
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
234
|
+
paranoid_clause = 'AND t2.deleted_at IS NULL' if ass.klass.paranoid?
|
235
|
+
# FIXME(uwe): Should we add "where" from the ScopeHolder here as well? Ref: DrilldownChanges#changes_for
|
236
|
+
min_query = <<~SQL
|
237
|
+
SELECT MIN(#{ass_order}) FROM #{include_table} t2 WHERE t2.#{fk_col} = #{model_table}.id #{paranoid_clause}
|
238
|
+
SQL
|
239
|
+
sql << " AND #{ass_order_prefixed} = (#{min_query})"
|
197
240
|
end
|
198
241
|
sql
|
199
242
|
else
|
@@ -225,13 +268,16 @@ module SimpleDrilldown
|
|
225
268
|
@list_includes = @@list_includes
|
226
269
|
@list_order = @@list_order
|
227
270
|
@dimension_defs = @@dimension_defs
|
228
|
-
|
271
|
+
@@summary_fields = [] unless defined?(@@summary_fields)
|
272
|
+
@summary_fields = @@summary_fields
|
273
|
+
|
274
|
+
@history_fields = @fields.select { |_k, v| v[:list_change_times] }.map { |k, _v| k.to_s }
|
229
275
|
end
|
230
276
|
|
231
277
|
# ?dimension[0]=supplier&dimension[1]=transaction_type&
|
232
278
|
# filter[year]=2009&filter[supplier][0]=Shell&filter[supplier][1]=Statoil
|
233
279
|
def index(do_render = true)
|
234
|
-
@search =
|
280
|
+
@search = new_search_object
|
235
281
|
|
236
282
|
@transaction_fields = (@search.fields + (@fields.keys.map(&:to_s) - @search.fields))
|
237
283
|
@transaction_fields_map = @fields
|
@@ -240,11 +286,9 @@ module SimpleDrilldown
|
|
240
286
|
includes = @base_includes.dup
|
241
287
|
|
242
288
|
@dimensions = []
|
243
|
-
select << ", 'All' as value0"
|
289
|
+
select << ", 'All'::text as value0"
|
244
290
|
@dimensions += @search.dimensions.map do |dn|
|
245
|
-
if @dimension_defs[dn].nil?
|
246
|
-
raise "Unknown distribution field: #{@search.dimensions.inspect}"
|
247
|
-
end
|
291
|
+
raise "Unknown distribution field: #{dn.inspect}" if @dimension_defs[dn].nil?
|
248
292
|
|
249
293
|
@dimension_defs[dn]
|
250
294
|
end
|
@@ -255,42 +299,108 @@ module SimpleDrilldown
|
|
255
299
|
|
256
300
|
conditions, @filter_text, filter_includes = self.class.make_conditions(@search.filter)
|
257
301
|
includes += filter_includes
|
258
|
-
includes.uniq!
|
302
|
+
includes.keep_if(&:present?).uniq!
|
259
303
|
if @search.order_by_value && @dimensions.size <= 1
|
260
|
-
order =
|
304
|
+
order = case @search.select_value
|
305
|
+
when DrilldownSearch::SelectValue::VOLUME
|
306
|
+
'volume DESC'
|
307
|
+
when DrilldownSearch::SelectValue::VOLUME_COMPENSATED
|
308
|
+
'volume_compensated DESC'
|
309
|
+
when DrilldownSearch::SelectValue::COUNT
|
310
|
+
'count DESC'
|
311
|
+
else
|
312
|
+
'count DESC'
|
313
|
+
end
|
261
314
|
else
|
262
|
-
order =
|
315
|
+
order = (1..@dimensions.size).map { |i| "value#{i}" }.join(',')
|
263
316
|
order = nil if order.empty?
|
264
317
|
end
|
265
|
-
group = (@base_group +
|
318
|
+
group = (@base_group + (1..@dimensions.size).map { |i| "value#{i}" }).join(',')
|
266
319
|
group = nil if group.empty?
|
267
320
|
|
321
|
+
joins = self.class.make_join([], @target_class.name.underscore.to_sym, includes)
|
268
322
|
rows = @target_class.unscoped.where(@base_condition).select(select).where(conditions)
|
269
|
-
.joins(
|
323
|
+
.joins(joins)
|
270
324
|
.group(group)
|
271
|
-
.order(order).
|
325
|
+
.order(order).to_a
|
272
326
|
|
273
327
|
if rows.empty?
|
274
328
|
@result = { value: 'All', count: 0, row_count: 0, nodes: 0, rows: [] }
|
329
|
+
@summary_fields.each { |f| @result[f] = 0 }
|
275
330
|
else
|
276
331
|
if do_render && @search.list && rows.inject(0) { |sum, r| sum + r[:count].to_i } > LIST_LIMIT
|
277
332
|
@search.list = false
|
278
|
-
flash[:notice] = "More than #{LIST_LIMIT} records. List disabled."
|
333
|
+
flash.now[:notice] = "More than #{LIST_LIMIT} records. List disabled."
|
279
334
|
end
|
280
335
|
@result = result_from_rows(rows, 0, 0, ['All'])
|
281
336
|
end
|
282
337
|
|
283
338
|
remove_duplicates(@result) unless @base_group.empty?
|
284
339
|
|
285
|
-
@remaining_dimensions = @dimension_defs.dup
|
286
|
-
|
287
|
-
|
340
|
+
@remaining_dimensions = @dimension_defs.dup
|
341
|
+
@remaining_dimensions.each_key do |dim_name|
|
342
|
+
if (@search.filter[dim_name] && @search.filter[dim_name].size == 1) ||
|
343
|
+
(@dimensions.any? { |d| d[:url_param_name] == dim_name })
|
344
|
+
@remaining_dimensions.delete(dim_name)
|
345
|
+
end
|
288
346
|
end
|
289
347
|
|
290
348
|
populate_list(conditions, includes, @result, []) if @search.list
|
291
349
|
render template: '/drilldown/index' if do_render
|
292
350
|
end
|
293
351
|
|
352
|
+
def choices
|
353
|
+
@search = new_search_object
|
354
|
+
dimension_name = params[:dimension_name]
|
355
|
+
dimension = @dimension_defs[dimension_name]
|
356
|
+
selected = @search.filter[dimension_name] || []
|
357
|
+
raise "Unknown dimension #{dimension_name.inspect}: #{@dimension_defs.keys.inspect}" unless dimension
|
358
|
+
|
359
|
+
choices = [[t(:all), nil]] +
|
360
|
+
(dimension[:legal_values]&.call(@search)&.map { |o| o.is_a?(Array) ? o[0..1].map(&:to_s) : o.to_s } || [])
|
361
|
+
choices_html = choices.map do |c|
|
362
|
+
%(<option value="#{c[1]}"#{' SELECTED' if selected.include?(c[1])}>#{c[0]}</option>)
|
363
|
+
end.join("\n")
|
364
|
+
render html: choices_html.html_safe
|
365
|
+
end
|
366
|
+
|
367
|
+
def html_export
|
368
|
+
index(false)
|
369
|
+
render template: '/drilldown/html_export', layout: 'print'
|
370
|
+
end
|
371
|
+
|
372
|
+
def excel_export
|
373
|
+
index(false)
|
374
|
+
headers['Content-Type'] = 'application/vnd.ms-excel'
|
375
|
+
headers['Content-Disposition'] = 'attachment; filename="transactions.xml"'
|
376
|
+
headers['Cache-Control'] = ''
|
377
|
+
render template: '/drilldown/excel_export', layout: false
|
378
|
+
end
|
379
|
+
|
380
|
+
def excel_export_transactions
|
381
|
+
params[:search][:list] = '1'
|
382
|
+
index(false)
|
383
|
+
@transactions = get_transactions(@result)
|
384
|
+
headers['Content-Type'] = 'application/vnd.ms-excel'
|
385
|
+
headers['Content-Disposition'] = 'attachment; filename="transactions.xml"'
|
386
|
+
render template: '/drilldown/excel_export_transactions', layout: false
|
387
|
+
end
|
388
|
+
|
389
|
+
def xml_export
|
390
|
+
params[:search][:list] = '1'
|
391
|
+
index(false)
|
392
|
+
@transactions = get_transactions(@result)
|
393
|
+
headers['Content-Type'] = 'text/xml'
|
394
|
+
headers['Content-Disposition'] = 'attachment; filename="transactions.xml"'
|
395
|
+
render template: '/drilldown/xml_export', layout: false
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
|
400
|
+
def new_search_object
|
401
|
+
SimpleDrilldown::Search.new(params[:search].to_unsafe_h, @default_fields, @default_select_value)
|
402
|
+
end
|
403
|
+
|
294
404
|
def remove_duplicates(result)
|
295
405
|
rows = result[:rows]
|
296
406
|
return 0 unless rows
|
@@ -301,6 +411,9 @@ module SimpleDrilldown
|
|
301
411
|
if prev_row
|
302
412
|
if prev_row[:value] == r[:value]
|
303
413
|
prev_row[:count] += r[:count]
|
414
|
+
@summary_fields.each do |f|
|
415
|
+
prev_row[f] += r[f]
|
416
|
+
end
|
304
417
|
prev_row[:row_count] = [prev_row[:row_count], r[:row_count]].max
|
305
418
|
prev_row[:nodes] = [prev_row[:nodes], r[:nodes]].max
|
306
419
|
prev_row[:rows] += r[:rows] if prev_row[:rows] || r[:rows]
|
@@ -322,7 +435,8 @@ module SimpleDrilldown
|
|
322
435
|
|
323
436
|
# Empty summary rows are needed to plot zero points in the charts
|
324
437
|
def add_zero_results(result_rows, dimension)
|
325
|
-
legal_values =
|
438
|
+
legal_values =
|
439
|
+
self.class.legal_values_for(@dimensions[dimension][:url_param_name], true).call(@search).map { |lv| lv[1] }
|
326
440
|
legal_values.reverse! if @dimensions[dimension][:reverse]
|
327
441
|
current_values = result_rows.map { |r| r[:value] }.compact
|
328
442
|
empty_values = legal_values - current_values
|
@@ -335,9 +449,8 @@ module SimpleDrilldown
|
|
335
449
|
row_count: 0,
|
336
450
|
nodes: 0
|
337
451
|
}
|
338
|
-
|
339
|
-
|
340
|
-
end
|
452
|
+
@summary_fields.each { |f| sub_result[f] = 0 }
|
453
|
+
sub_result[:rows] = add_zero_results([], dimension + 1) if dimension < @dimensions.size - 1
|
341
454
|
result_rows << sub_result
|
342
455
|
end
|
343
456
|
result_rows = result_rows.sort_by { |r| legal_values.index(r[:value]) }
|
@@ -353,12 +466,14 @@ module SimpleDrilldown
|
|
353
466
|
return nil if values != previous_values
|
354
467
|
|
355
468
|
if dimension == @dimensions.size
|
356
|
-
|
469
|
+
result = {
|
357
470
|
value: values[-1],
|
358
471
|
count: row[:count].to_i,
|
359
472
|
row_count: 1,
|
360
473
|
nodes: @search.list ? 2 : 1
|
361
474
|
}
|
475
|
+
@summary_fields.each { |f| result[f] = row[f].to_i }
|
476
|
+
return result
|
362
477
|
end
|
363
478
|
|
364
479
|
result_rows = []
|
@@ -373,61 +488,84 @@ module SimpleDrilldown
|
|
373
488
|
|
374
489
|
result_rows = add_zero_results(result_rows, dimension)
|
375
490
|
|
376
|
-
{
|
491
|
+
result = {
|
377
492
|
value: values[-1],
|
378
493
|
count: result_rows.inject(0) { |t, r| t + r[:count].to_i },
|
379
494
|
row_count: result_rows.inject(0) { |t, r| t + r[:row_count] },
|
380
495
|
nodes: result_rows.inject(0) { |t, r| t + r[:nodes] } + 1,
|
381
496
|
rows: result_rows
|
382
497
|
}
|
498
|
+
@summary_fields.each { |f| result[f] = result_rows.inject(0) { |t, r| t + r[f] } }
|
499
|
+
result
|
383
500
|
end
|
384
501
|
|
385
|
-
def html_export
|
386
|
-
index(false)
|
387
|
-
render template: '/drilldown/html_export', layout: '../drilldown/print'
|
388
|
-
end
|
389
|
-
|
390
|
-
def excel_export
|
391
|
-
index(false)
|
392
|
-
headers['Content-Type'] = 'application/vnd.ms-excel'
|
393
|
-
headers['Content-Disposition'] = 'attachment; filename="elections.xls"'
|
394
|
-
headers['Cache-Control'] = ''
|
395
|
-
render template: '/drilldown/excel_export', layout: false
|
396
|
-
end
|
397
|
-
|
398
|
-
private
|
399
|
-
|
400
502
|
def populate_list(conditions, includes, result, values)
|
401
503
|
if result[:rows]
|
402
504
|
result[:rows].each do |r|
|
403
505
|
populate_list(conditions, includes, r, values + [r[:value]])
|
404
506
|
end
|
405
507
|
else
|
406
|
-
|
508
|
+
list_includes = includes + @list_includes
|
407
509
|
@search.fields.each do |field|
|
408
510
|
field_def = @transaction_fields_map[field.to_sym]
|
409
511
|
raise "Field definition missing for: #{field.inspect}" unless field_def
|
410
512
|
|
411
513
|
field_includes = field_def[:include]
|
412
514
|
if field_includes
|
413
|
-
|
515
|
+
list_includes += field_includes.is_a?(Array) ? field_includes : [field_includes]
|
414
516
|
end
|
415
517
|
end
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
518
|
+
list_includes.uniq!
|
519
|
+
if @search.list_change_times
|
520
|
+
@history_fields.each do |f|
|
521
|
+
list_includes << { assignment: { order: :"#{f}_changes" } } if @search.fields.include? f
|
522
|
+
end
|
523
|
+
end
|
524
|
+
joins = self.class.make_join([], @target_class.name.underscore.to_sym, list_includes)
|
525
|
+
list_conditions = list_conditions(conditions, values)
|
526
|
+
base_query = @target_class.unscoped.where(@base_condition).joins(joins).order(@list_order)
|
527
|
+
base_query = base_query.where(list_conditions) if list_conditions
|
528
|
+
result[:transactions] = base_query.to_a
|
420
529
|
end
|
421
530
|
end
|
422
531
|
|
423
532
|
def list_conditions(conditions, values)
|
533
|
+
conditions ||= ['']
|
534
|
+
|
424
535
|
list_conditions_string = conditions[0].dup
|
425
536
|
@dimensions.each do |d|
|
426
|
-
list_conditions_string << "#{unless list_conditions_string.empty?
|
427
|
-
' AND '
|
428
|
-
end}#{d[:select_expression]} = ?"
|
537
|
+
list_conditions_string << "#{' AND ' unless list_conditions_string.empty?}#{d[:select_expression]} = ?"
|
429
538
|
end
|
430
539
|
[list_conditions_string, *(conditions[1..-1] + values)]
|
431
540
|
end
|
541
|
+
|
542
|
+
def get_transactions(tree)
|
543
|
+
return tree[:transactions] if tree[:transactions]
|
544
|
+
|
545
|
+
tree[:rows].map { |r| get_transactions(r) }.flatten
|
546
|
+
end
|
547
|
+
|
548
|
+
class ScopeHolder
|
549
|
+
def initialize(scope)
|
550
|
+
instance_eval(&scope)
|
551
|
+
end
|
552
|
+
|
553
|
+
def order(order)
|
554
|
+
@order = order
|
555
|
+
self
|
556
|
+
end
|
557
|
+
|
558
|
+
def where(*_conditions)
|
559
|
+
self
|
560
|
+
end
|
561
|
+
|
562
|
+
def to_s
|
563
|
+
if @order.is_a?(Hash)
|
564
|
+
@order.map { |field, direction| "#{field} #{direction}" }.join(', ')
|
565
|
+
else
|
566
|
+
@order.to_s
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
432
570
|
end
|
433
571
|
end
|
@@ -9,7 +9,7 @@ module SimpleDrilldown
|
|
9
9
|
|
10
10
|
def caption
|
11
11
|
result = @search.title ? @search.title : "#{@target_class} #{t(@search.select_value.downcase)}" +
|
12
|
-
|
12
|
+
((@dimensions && @dimensions.any?) ? ' by ' + @dimensions.map { |d| d[:pretty_name] }.join(' and ') : '')
|
13
13
|
result.gsub('$date', [*@search.filter[:calendar_date]].uniq.join(' - '))
|
14
14
|
end
|
15
15
|
|
@@ -4,8 +4,8 @@ module SimpleDrilldown
|
|
4
4
|
class Engine < ::Rails::Engine
|
5
5
|
isolate_namespace SimpleDrilldown
|
6
6
|
|
7
|
-
initializer
|
8
|
-
app.config.assets.precompile += %w
|
7
|
+
initializer 'simple_drilldown.assets.precompile' do |app|
|
8
|
+
app.config.assets.precompile += %w[chartkick.js]
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_model/naming'
|
2
4
|
|
3
5
|
module SimpleDrilldown
|
@@ -14,6 +16,7 @@ module SimpleDrilldown
|
|
14
16
|
module SelectValue
|
15
17
|
COUNT = 'COUNT'
|
16
18
|
VOLUME = 'VOLUME'
|
19
|
+
VOLUME_COMPENSATED = 'VOLUME_COMPENSATED'
|
17
20
|
end
|
18
21
|
|
19
22
|
attr_reader :dimensions
|
@@ -22,14 +25,22 @@ module SimpleDrilldown
|
|
22
25
|
attr_reader :filter
|
23
26
|
attr_accessor :list
|
24
27
|
attr_accessor :percent
|
25
|
-
attr_reader :
|
28
|
+
attr_reader :list_change_times
|
26
29
|
attr_reader :order_by_value
|
27
30
|
attr_reader :select_value
|
28
31
|
attr_reader :title
|
29
32
|
attr_reader :default_fields
|
30
33
|
|
31
|
-
def
|
32
|
-
|
34
|
+
def self.validators_on(_attribute)
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.human_attribute_name(attribute)
|
39
|
+
attribute
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(attributes_or_search, default_fields = nil, default_select_value = SelectValue::COUNT)
|
43
|
+
if attributes_or_search.is_a? self.class
|
33
44
|
s = attributes_or_search
|
34
45
|
@dimensions = s.dimensions.dup
|
35
46
|
@display_type = s.display_type.dup
|
@@ -37,7 +48,7 @@ module SimpleDrilldown
|
|
37
48
|
@filter = s.filter.dup
|
38
49
|
@list = s.list
|
39
50
|
@percent = s.percent
|
40
|
-
@
|
51
|
+
@list_change_times = s.list_change_times
|
41
52
|
@order_by_value = s.order_by_value
|
42
53
|
@select_value = s.select_value.dup
|
43
54
|
@title = s.title
|
@@ -45,45 +56,48 @@ module SimpleDrilldown
|
|
45
56
|
else
|
46
57
|
attributes = attributes_or_search
|
47
58
|
@default_fields = default_fields
|
59
|
+
@default_select_value = default_select_value
|
48
60
|
@dimensions = attributes && attributes[:dimensions] || []
|
49
|
-
@dimensions.delete_if
|
61
|
+
@dimensions.delete_if(&:empty?)
|
50
62
|
@filter = attributes && attributes[:filter] ? attributes[:filter] : {}
|
51
|
-
@filter.each { |k
|
52
|
-
@filter.
|
53
|
-
|
54
|
-
|
55
|
-
@display_type = DisplayType::BAR
|
63
|
+
@filter.keys.dup.each { |k| @filter[k] = [*@filter[k]] }
|
64
|
+
@filter.each do |_k, v|
|
65
|
+
v.delete('')
|
66
|
+
v.delete('Select Some Options')
|
56
67
|
end
|
68
|
+
@filter.delete_if { |_k, v| v.empty? }
|
69
|
+
@display_type = attributes && attributes[:display_type] ? attributes[:display_type] : DisplayType::NONE
|
70
|
+
@display_type = DisplayType::BAR if @dimensions.size >= 2 && @display_type == DisplayType::PIE
|
57
71
|
|
58
72
|
@order_by_value = attributes && (attributes[:order_by_value] == '1')
|
59
|
-
@select_value = attributes
|
60
|
-
@list = attributes
|
73
|
+
@select_value = attributes&.dig(:select_value).present? ? attributes[:select_value] : @default_select_value
|
74
|
+
@list = attributes&.[](:list) == '1'
|
61
75
|
@percent = attributes&.[](:percent) == '1'
|
62
|
-
@
|
63
|
-
if
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@title = attributes[:title] if attributes
|
76
|
+
@list_change_times = attributes&.[](:list_change_times) == '1'
|
77
|
+
@fields = if attributes && attributes[:fields]
|
78
|
+
if attributes[:fields].is_a?(Array)
|
79
|
+
attributes[:fields]
|
80
|
+
else
|
81
|
+
attributes[:fields].to_h.select { |_k, v| v == '1' }.map { |k, _v| k }
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@default_fields
|
85
|
+
end
|
86
|
+
@title = attributes[:title] if attributes&.dig(:title).present?
|
73
87
|
end
|
74
88
|
end
|
75
89
|
|
76
90
|
def url_options
|
77
91
|
o = {
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:last_change_time => last_change_time ? '1' : '0',
|
82
|
-
:filter => filter,
|
92
|
+
search: {
|
93
|
+
title: title,
|
94
|
+
list: list ? '1' : '0',
|
83
95
|
percent: percent ? '1' : '0',
|
84
|
-
:
|
85
|
-
:
|
86
|
-
|
96
|
+
list_change_times: list_change_times ? '1' : '0',
|
97
|
+
filter: filter,
|
98
|
+
dimensions: dimensions,
|
99
|
+
display_type: display_type,
|
100
|
+
},
|
87
101
|
}
|
88
102
|
o[:search][:fields] = fields unless fields == @default_fields
|
89
103
|
o
|
@@ -95,8 +109,9 @@ module SimpleDrilldown
|
|
95
109
|
end
|
96
110
|
|
97
111
|
def drill_down(dimensions, *values)
|
98
|
-
raise
|
99
|
-
|
112
|
+
raise 'Too many values' if values.size > self.dimensions.size
|
113
|
+
|
114
|
+
s = self.class.new(self)
|
100
115
|
values.each_with_index { |v, i| s.filter[dimensions[i][:url_param_name]] = [v] }
|
101
116
|
values.size.times { s.dimensions.shift }
|
102
117
|
s
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_drilldown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Uwe Kubosch
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2020-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: chartkick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.3'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rails
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -30,20 +44,6 @@ dependencies:
|
|
30
44
|
- - "<"
|
31
45
|
- !ruby/object:Gem::Version
|
32
46
|
version: '7'
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: chartkick
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '3.3'
|
40
|
-
type: :runtime
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '3.3'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rubocop
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|