compendium 1.1.3.4 → 1.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.
Files changed (44) hide show
  1. checksums.yaml +5 -13
  2. data/.travis.yml +12 -0
  3. data/CHANGELOG.md +18 -0
  4. data/Gemfile +4 -0
  5. data/README.md +185 -4
  6. data/app/assets/stylesheets/compendium/options.css.scss +2 -3
  7. data/app/classes/compendium/presenters/chart.rb +1 -1
  8. data/app/classes/compendium/presenters/csv.rb +32 -0
  9. data/app/classes/compendium/presenters/query.rb +4 -2
  10. data/app/classes/compendium/presenters/settings/query.rb +10 -2
  11. data/app/classes/compendium/presenters/settings/table.rb +24 -4
  12. data/app/classes/compendium/presenters/table.rb +33 -18
  13. data/app/controllers/compendium/reports_controller.rb +28 -7
  14. data/app/views/compendium/reports/setup.haml +16 -2
  15. data/compendium.gemspec +4 -3
  16. data/config/locales/en.yml +6 -1
  17. data/config/locales/es.yml +11 -0
  18. data/config/locales/fr.yml +11 -0
  19. data/lib/compendium.rb +1 -0
  20. data/lib/compendium/collection_query.rb +1 -1
  21. data/lib/compendium/count_query.rb +7 -1
  22. data/lib/compendium/dsl.rb +45 -4
  23. data/lib/compendium/engine/mount.rb +13 -5
  24. data/lib/compendium/errors.rb +6 -0
  25. data/lib/compendium/metric.rb +1 -1
  26. data/lib/compendium/open_hash.rb +1 -1
  27. data/lib/compendium/param_types.rb +2 -2
  28. data/lib/compendium/params.rb +1 -1
  29. data/lib/compendium/query.rb +23 -4
  30. data/lib/compendium/report.rb +14 -10
  31. data/lib/compendium/sum_query.rb +3 -1
  32. data/lib/compendium/through_query.rb +7 -2
  33. data/lib/compendium/version.rb +1 -1
  34. data/spec/count_query_spec.rb +41 -5
  35. data/spec/dsl_spec.rb +77 -2
  36. data/spec/presenters/csv_spec.rb +30 -0
  37. data/spec/presenters/settings/query_spec.rb +26 -0
  38. data/spec/presenters/settings/table_spec.rb +64 -0
  39. data/spec/presenters/table_spec.rb +83 -0
  40. data/spec/query_spec.rb +55 -8
  41. data/spec/report_spec.rb +24 -1
  42. data/spec/sum_query_spec.rb +40 -5
  43. metadata +73 -30
  44. data/config/initializers/ruby/hash.rb +0 -6
@@ -5,8 +5,8 @@ module Compendium
5
5
 
6
6
  before_filter :find_report
7
7
  before_filter :find_query
8
- before_filter :validate_options, only: :run
9
- before_filter :run_report, only: :run
8
+ before_filter :validate_options, only: [:run, :export]
9
+ before_filter :run_report, only: [:run, :export]
10
10
 
11
11
  def setup
12
12
  render_setup
@@ -25,6 +25,27 @@ module Compendium
25
25
  end
26
26
  end
27
27
 
28
+ def export
29
+ unless @report.exports?(request.format)
30
+ redirect_to action: :setup, format: nil
31
+ return
32
+ end
33
+
34
+ respond_to do |format|
35
+ format.csv do
36
+ filename = @report.report_name.to_s.parameterize + '-' + Time.current.strftime('%Y%m%d%H%I%S')
37
+ response.headers['Content-Disposition'] = 'attachment; filename="' + filename + '.csv"'
38
+
39
+ query = @report.queries[@report.exporters[:csv]]
40
+ render text: query.render_csv
41
+ end
42
+
43
+ format.any do
44
+ redirect_to action: :setup, format: nil
45
+ end
46
+ end
47
+ end
48
+
28
49
  private
29
50
 
30
51
  def find_report
@@ -32,11 +53,11 @@ module Compendium
32
53
  @report_name = "#{@prefix}_report"
33
54
 
34
55
  begin
35
- require(@report_name) unless Rails.env.development? or Module.const_defined?(@report_name.classify)
56
+ require(@report_name) unless Rails.env.development? || Module.const_defined?(@report_name.classify)
36
57
  @report_class = @report_name.camelize.constantize
37
58
  @report = setup_report
38
59
  rescue LoadError
39
- flash[:error] = t(:invalid_report)
60
+ flash[:error] = t(:invalid_report, scope: 'compendium.reports')
40
61
  redirect_to action: :index
41
62
  end
42
63
  end
@@ -46,7 +67,7 @@ module Compendium
46
67
  @query = @report.queries[params[:query]]
47
68
 
48
69
  unless @query
49
- flash[:error] = t(:invalid_report_query)
70
+ flash[:error] = t(:invalid_report_query, scope: 'compendium.reports')
50
71
  redirect_to action: :setup, report_name: params[:report_name]
51
72
  end
52
73
  end
@@ -61,7 +82,7 @@ module Compendium
61
82
  end
62
83
 
63
84
  def validate_options
64
- render_setup and return unless @report.valid?
85
+ render_setup && return unless @report.valid?
65
86
  end
66
87
 
67
88
  def run_report
@@ -80,4 +101,4 @@ module Compendium
80
101
  paths
81
102
  end
82
103
  end
83
- end
104
+ end
@@ -4,11 +4,25 @@
4
4
  = render_if_exists partial: 'report_header', path: 'compendium/reports'
5
5
 
6
6
  .options
7
- = form_for report, as: :report, url: compendium_reports_run_path do |f|
7
+ = form_for report, as: :report, url: compendium_reports_run_path, html: { id: 'setup_report_form' } do |f|
8
+ = hidden_field_tag :format, :html
9
+
8
10
  - report.options.each do |option|
9
11
  - expose option, Compendium::Presenters::Option do |opt|
10
12
  .option
11
13
  = opt.label(f)
12
14
  .option-element-group= opt.input(self, f)
13
15
 
14
- = f.submit t(:generate_report, scope: 'compendium.reports')
16
+ .option
17
+ = f.submit t(:generate_report, scope: 'compendium.reports'), onclick: "set_format('html')"
18
+ - if report.exports?(:csv)
19
+ = f.submit t(:export_csv, scope: 'compendium.reports'), name: :export, onclick: "set_format('csv')"
20
+
21
+ :javascript
22
+ function set_format(format)
23
+ {
24
+ var form = document.getElementById('setup_report_form'),
25
+ input = form.querySelector('input#format[type=hidden]');
26
+
27
+ input.value = format;
28
+ }
data/compendium.gemspec CHANGED
@@ -18,10 +18,11 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency 'rails', '>= 3.0.0'
21
+ gem.add_dependency 'rails', '>= 3.0.0', '< 4'
22
22
  gem.add_dependency 'sass-rails', '>= 3.0.0'
23
23
  gem.add_dependency 'compass-rails', '>= 1.0.0'
24
- gem.add_dependency 'collection_of', '1.0.5'
24
+ gem.add_dependency 'collection_of', '1.0.6'
25
25
  gem.add_dependency 'inheritable_attr', '>= 1.0.0'
26
- gem.add_development_dependency 'rspec', '~> 2.0'
26
+ gem.add_development_dependency 'rake', '> 11.0.1', '< 12'
27
+ gem.add_development_dependency 'rspec', '~> 2.0', '< 2.99'
27
28
  end
@@ -1,6 +1,11 @@
1
1
  en:
2
2
  compendium:
3
+ total: "Total"
4
+
3
5
  reports:
6
+ generate_report: "Generate Report"
7
+ export_csv: "Export CSV"
8
+
4
9
  invalid_report: "Invalid report!"
5
10
  invalid_report_query: "Invalid query!"
6
- generate_report: "Generate Report"
11
+ query_required_for_csv: "A query must be specified to export a CSV file!"
@@ -0,0 +1,11 @@
1
+ es:
2
+ compendium:
3
+ total: "Total"
4
+
5
+ reports:
6
+ generate_report: "Generar informe"
7
+ export_csv: "Exportar CSV"
8
+
9
+ invalid_report: "¡Informe no válido!"
10
+ invalid_report_query: "¡Consulta no válida!"
11
+ query_required_for_csv: "¡Se debe especificar una consulta para exportar un archivo CSV!"
@@ -0,0 +1,11 @@
1
+ fr:
2
+ compendium:
3
+ total: "Total"
4
+
5
+ reports:
6
+ generate_report: "Générer Rapport"
7
+ export_csv: "Exporter CSV"
8
+
9
+ invalid_report: "Rapport invalide!"
10
+ invalid_report_query: "Requête invalide!"
11
+ query_required_for_csv: "Une requête doit être spécifiée afin d'exporter un fichier CSV!"
data/lib/compendium.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'compendium/engine'
2
+ require 'compendium/errors'
2
3
  require 'compendium/version'
3
4
  require 'active_support/configurable'
4
5
 
@@ -37,7 +37,7 @@ module Compendium
37
37
  end
38
38
 
39
39
  def prepare_collection(collection)
40
- return collection if collection.is_a?(Query) or collection.is_a?(Symbol)
40
+ return collection if collection.is_a?(Query) || collection.is_a?(Symbol)
41
41
  collection.is_a?(Hash) ? collection : Hash[collection.zip(collection)]
42
42
  end
43
43
  end
@@ -1,10 +1,16 @@
1
+ require 'compendium/errors'
1
2
  require 'compendium/query'
2
3
 
3
4
  module Compendium
4
5
  # A CountQuery is a Query which runs an SQL count statement
5
6
  # Often useful in conjunction with a grouped query
6
7
  class CountQuery < Query
7
- InvalidCommand = Class.new(StandardError)
8
+
9
+ def initialize(*args)
10
+ super
11
+
12
+ @options.reverse_merge!(order: 'COUNT(*)', reverse: true)
13
+ end
8
14
 
9
15
  private
10
16
 
@@ -8,14 +8,17 @@ module Compendium
8
8
  def self.extended(klass)
9
9
  klass.inheritable_attr :queries, default: ::Collection[Query]
10
10
  klass.inheritable_attr :options, default: ::Collection[Option]
11
+ klass.inheritable_attr :exporters, default: {}
11
12
  end
12
13
 
14
+ # Define a query
13
15
  def query(name, opts = {}, &block)
14
16
  define_query(name, opts, &block)
15
17
  end
16
18
  alias_method :chart, :query
17
19
  alias_method :data, :query
18
20
 
21
+ # Define a parameter for the report
19
22
  def option(name, *args)
20
23
  opts = args.extract_options!
21
24
  type = args.shift
@@ -31,6 +34,8 @@ module Compendium
31
34
  end
32
35
  end
33
36
 
37
+ # Define a metric from a query or implicitly
38
+ # A metric is a derived statistic from a report, for instance a count of rows
34
39
  def metric(name, *args, &block)
35
40
  proc = args.first.is_a?(Proc) ? args.first : block
36
41
  opts = args.extract_options!
@@ -49,10 +54,32 @@ module Compendium
49
54
  end
50
55
  end
51
56
 
57
+ # Define a filter to modify the results from specified query (in this case :deliveries)
58
+ # For example, this can be useful to translate columns prior to rendering, as it will apply
59
+ # for all render types (table, chart, JSON)
60
+ # Multiple queries can be set up with the same filter
52
61
  def filter(*query_names, &block)
53
- query_names.each do |query_name|
54
- raise ArgumentError, "query #{query_name} is not defined" unless queries.key?(query_name)
55
- queries[query_name].add_filter(block)
62
+ each_query(query_names) do |query|
63
+ query.add_filter(block)
64
+ end
65
+ end
66
+
67
+ # Allow default table settings to be defined for a query.
68
+ # These settings are used when rendering a query to an HTML table or to CSV
69
+ def table(*query_names, &block)
70
+ each_query(query_names) do |query|
71
+ query.table_settings = block
72
+ end
73
+ end
74
+
75
+ # Define any exports the report has
76
+ def exports(type, *opts)
77
+ exporters[type] = if opts.empty?
78
+ true
79
+ elsif opts.length == 1
80
+ opts.first
81
+ else
82
+ opts
56
83
  end
57
84
  end
58
85
 
@@ -86,6 +113,13 @@ module Compendium
86
113
 
87
114
  private
88
115
 
116
+ def each_query(query_names, &block)
117
+ query_names.each do |query_name|
118
+ raise ArgumentError, "query #{query_name} is not defined" unless queries.key?(query_name)
119
+ yield queries[query_name]
120
+ end
121
+ end
122
+
89
123
  def define_query(name, opts, &block)
90
124
  params = [name.to_sym, opts, block]
91
125
  query_type = Query
@@ -110,7 +144,14 @@ module Compendium
110
144
  query.report = self
111
145
 
112
146
  metrics[name] = opts[:metric] if opts.key?(:metric)
147
+
148
+ if queries[name]
149
+ raise CannotRedefineQueryType unless queries[name].instance_of?(query_type)
150
+ queries.delete(name)
151
+ end
152
+
113
153
  queries << query
154
+
114
155
  query
115
156
  end
116
157
 
@@ -119,4 +160,4 @@ module Compendium
119
160
  self.params_class.validates name, validations
120
161
  end
121
162
  end
122
- end
163
+ end
@@ -1,13 +1,21 @@
1
1
  module ActionDispatch
2
2
  module Routing
3
3
  class Mapper
4
+ class ExportRouter
5
+ def matches?(request)
6
+ request.params[:export].present?
7
+ end
8
+ end
9
+
4
10
  def mount_compendium(options = {})
5
- scope options[:at], controller: options.fetch(:controller, 'compendium/reports') do
6
- get ':report_name', action: :setup, as: 'compendium_reports_setup'
7
- post ':report_name(/:query)', action: :run, as: 'compendium_reports_run'
8
- root action: :index, as: 'compendium_reports_root'
11
+ scope options[:at], controller: options.fetch(:controller, 'compendium/reports'), as: 'compendium_reports' do
12
+ get ':report_name', action: :setup, constraints: { format: :html }, as: 'setup'
13
+ match ':report_name/export', action: :export, as: 'export', via: [:get, :post]
14
+ post ':report_name(/:query)', constraints: ExportRouter.new, action: :export, as: 'export_post'
15
+ match ':report_name(/:query)', action: :run, as: 'run', via: [:get, :post]
16
+ root action: :index, as: 'root'
9
17
  end
10
18
  end
11
19
  end
12
20
  end
13
- end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Compendium
2
+ CompendiumError = Class.new(StandardError)
3
+
4
+ InvalidCommand = Class.new(CompendiumError)
5
+ CannotRedefineQueryType = Class.new(CompendiumError)
6
+ end
@@ -27,7 +27,7 @@ module Compendium
27
27
  private
28
28
 
29
29
  def condition_failed?(ctx)
30
- (options.key?(:if) and !ctx.instance_exec(&options[:if])) or (options.key?(:unless) and ctx.instance_exec(&options[:unless]))
30
+ (options.key?(:if) && !ctx.instance_exec(&options[:if])) || (options.key?(:unless) && ctx.instance_exec(&options[:unless]))
31
31
  end
32
32
  end
33
33
  end
@@ -45,7 +45,7 @@ module Compendium
45
45
  return self[method[1..-1]] if self.key?(method[1..-1].to_sym)
46
46
 
47
47
  else
48
- return self[method] if key?(method) or !respond_to?(method)
48
+ return self[method] if key?(method) || !respond_to?(method)
49
49
  end
50
50
 
51
51
  super
@@ -38,7 +38,7 @@ module Compendium
38
38
  index = obj
39
39
  else
40
40
  index = obj.numeric? ? obj.to_i : @choices.index(obj)
41
- raise IndexError if (!obj.nil? and index.nil?) or index.to_i.abs > @choices.length - 1
41
+ raise IndexError if (!obj.nil? && index.nil?) || index.to_i.abs > @choices.length - 1
42
42
  end
43
43
 
44
44
  super(index)
@@ -63,7 +63,7 @@ module Compendium
63
63
  class BooleanParam < Param
64
64
  def initialize(obj, *)
65
65
  # If given 0, 1, or a version thereof (ie. "0"), pass it along
66
- return super obj.to_i if obj.numeric? and (0..1).cover?(obj.to_i)
66
+ return super obj.to_i if obj.numeric? && (0..1).cover?(obj.to_i)
67
67
  super !!obj ? 0 : 1
68
68
  end
69
69
 
@@ -37,7 +37,7 @@ module Compendium
37
37
  end
38
38
 
39
39
  def get_default_value(current, default)
40
- if current.blank? and !default.blank?
40
+ if current.blank? && !default.blank?
41
41
  default.respond_to?(:call) ? default.call : default
42
42
  else
43
43
  current
@@ -4,12 +4,11 @@ require 'compendium/metric'
4
4
  require 'compendium/presenters/chart'
5
5
  require 'compendium/presenters/table'
6
6
  require 'collection_of'
7
- require_relative '../../config/initializers/ruby/hash'
8
7
 
9
8
  module Compendium
10
9
  class Query
11
10
  attr_reader :name, :results, :metrics, :filters
12
- attr_accessor :options, :proc, :report
11
+ attr_accessor :options, :proc, :report, :table_settings
13
12
 
14
13
  def initialize(*args)
15
14
  @report = args.shift if arg_is_report?(args.first)
@@ -58,6 +57,10 @@ module Compendium
58
57
  Compendium::Presenters::Table.new(template, self, *options, &block).render unless empty?
59
58
  end
60
59
 
60
+ def render_csv(&block)
61
+ Compendium::Presenters::CSV.new(self, &block).render unless empty?
62
+ end
63
+
61
64
  # Allow access to the chart object without having to explicitly render it
62
65
  def chart(template, *options, &block)
63
66
  # Access the actual chart object
@@ -89,6 +92,8 @@ module Compendium
89
92
 
90
93
  def collect_results(context, *params)
91
94
  command = context.instance_exec(*params, &proc) if proc
95
+ command = order_command(command) if options[:order]
96
+
92
97
  results = fetch_results(command)
93
98
  results = filter_results(results, *params) if filters.any?
94
99
  @results = ResultSet.new(results) if results
@@ -105,6 +110,12 @@ module Compendium
105
110
  def filter_results(results, params)
106
111
  return unless results
107
112
 
113
+ if results.respond_to? :with_indifferent_access
114
+ results = results.with_indifferent_access
115
+ else
116
+ results.map! &:with_indifferent_access
117
+ end
118
+
108
119
  filters.each do |f|
109
120
  if f.arity == 2
110
121
  results = f.call(results, params)
@@ -116,6 +127,14 @@ module Compendium
116
127
  results
117
128
  end
118
129
 
130
+ def order_command(command)
131
+ return command unless command.respond_to?(:order)
132
+
133
+ command = command.order(options[:order])
134
+ command = command.reverse_order if options.fetch(:reverse, false)
135
+ command
136
+ end
137
+
119
138
  def execute_command(command)
120
139
  return [] if command.nil?
121
140
  command = command.to_sql if command.respond_to?(:to_sql)
@@ -127,11 +146,11 @@ module Compendium
127
146
  end
128
147
 
129
148
  def arg_is_report?(arg)
130
- arg.is_a?(Report) or (arg.is_a?(Class) and arg < Report)
149
+ arg.is_a?(Report) || (arg.is_a?(Class) && arg < Report)
131
150
  end
132
151
 
133
152
  def get_associated_query(query)
134
153
  query.is_a?(Query) ? query : report.queries[query]
135
154
  end
136
155
  end
137
- end
156
+ end