simple_drilldown 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|