report_cat 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.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +147 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/javascripts/report_cat/application.js +13 -0
  6. data/app/assets/stylesheets/report_cat/application.css +13 -0
  7. data/app/controllers/report_cat/reports_controller.rb +43 -0
  8. data/app/helpers/report_cat/reports_helper.rb +132 -0
  9. data/app/models/report_cat/date_range.rb +94 -0
  10. data/app/views/report_cat/reports/_google_charts.html.erb +61 -0
  11. data/app/views/report_cat/reports/index.html.erb +2 -0
  12. data/app/views/report_cat/reports/show.html.erb +15 -0
  13. data/config/locales/en.yml +26 -0
  14. data/config/routes.rb +7 -0
  15. data/db/migrate/20130918075200_create_date_ranges.rb +13 -0
  16. data/lib/report_cat/config.rb +30 -0
  17. data/lib/report_cat/core/chart.rb +50 -0
  18. data/lib/report_cat/core/column.rb +70 -0
  19. data/lib/report_cat/core/param.rb +35 -0
  20. data/lib/report_cat/core/report.rb +127 -0
  21. data/lib/report_cat/engine.rb +11 -0
  22. data/lib/report_cat/matchers/have_chart.rb +58 -0
  23. data/lib/report_cat/matchers/have_column.rb +45 -0
  24. data/lib/report_cat/matchers/have_param.rb +52 -0
  25. data/lib/report_cat/reports/cohort_report.rb +113 -0
  26. data/lib/report_cat/reports/date_range_report.rb +66 -0
  27. data/lib/report_cat/version.rb +3 -0
  28. data/lib/report_cat.rb +39 -0
  29. data/lib/tasks/report_cat.rake +4 -0
  30. data/spec/controllers/report_cat/reports_controller_spec.rb +100 -0
  31. data/spec/coverage_spec.rb +18 -0
  32. data/spec/data/helpers/report_charts.html +1 -0
  33. data/spec/data/helpers/report_charts.html.tmp +1 -0
  34. data/spec/data/helpers/report_form.html +63 -0
  35. data/spec/data/helpers/report_form.html.tmp +63 -0
  36. data/spec/data/helpers/report_form_param.html +1 -0
  37. data/spec/data/helpers/report_form_param.html.tmp +1 -0
  38. data/spec/data/helpers/report_list.html +1 -0
  39. data/spec/data/helpers/report_list.html.tmp +1 -0
  40. data/spec/data/helpers/report_param_checkbox.html +1 -0
  41. data/spec/data/helpers/report_param_checkbox.html.tmp +1 -0
  42. data/spec/data/helpers/report_param_date.html +60 -0
  43. data/spec/data/helpers/report_param_date.html.tmp +60 -0
  44. data/spec/data/helpers/report_param_hidden.html +1 -0
  45. data/spec/data/helpers/report_param_hidden.html.tmp +1 -0
  46. data/spec/data/helpers/report_param_select.html +3 -0
  47. data/spec/data/helpers/report_param_select.html.tmp +3 -0
  48. data/spec/data/helpers/report_param_text_field.html +1 -0
  49. data/spec/data/helpers/report_param_text_field.html.tmp +1 -0
  50. data/spec/data/helpers/report_table.html +1 -0
  51. data/spec/data/helpers/report_table.html.tmp +1 -0
  52. data/spec/data/helpers/report_table_hidden.html +1 -0
  53. data/spec/data/helpers/report_table_hidden.html.tmp +1 -0
  54. data/spec/data/lib/chart_columns.json +1 -0
  55. data/spec/data/lib/chart_columns.json.tmp +1 -0
  56. data/spec/data/lib/chart_data.json +1 -0
  57. data/spec/data/lib/chart_data.json.tmp +1 -0
  58. data/spec/data/lib/date_range_report_where.sql +6 -0
  59. data/spec/data/lib/date_range_report_where.sql.tmp +6 -0
  60. data/spec/data/lib/report.csv +3 -0
  61. data/spec/data/lib/report.csv.tmp +3 -0
  62. data/spec/data/lib/report.sql +1 -0
  63. data/spec/data/lib/report.sql.tmp +1 -0
  64. data/spec/data/models/sql_intersect.sql +5 -0
  65. data/spec/data/models/sql_intersect.sql.tmp +5 -0
  66. data/spec/dummy/README.rdoc +28 -0
  67. data/spec/dummy/Rakefile +6 -0
  68. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  69. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  70. data/spec/dummy/app/controllers/application_controller.rb +21 -0
  71. data/spec/dummy/app/controllers/root_controller.rb +20 -0
  72. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  73. data/spec/dummy/app/models/user.rb +13 -0
  74. data/spec/dummy/app/models/visit.rb +16 -0
  75. data/spec/dummy/app/reports/retention_cohort_report.rb +19 -0
  76. data/spec/dummy/app/reports/retention_report.rb +30 -0
  77. data/spec/dummy/app/reports/user_report.rb +23 -0
  78. data/spec/dummy/app/views/layouts/admin.html.erb +19 -0
  79. data/spec/dummy/app/views/layouts/application.html.erb +19 -0
  80. data/spec/dummy/app/views/root/index.html.erb +8 -0
  81. data/spec/dummy/bin/bundle +3 -0
  82. data/spec/dummy/bin/rails +4 -0
  83. data/spec/dummy/bin/rake +4 -0
  84. data/spec/dummy/config/application.rb +30 -0
  85. data/spec/dummy/config/boot.rb +5 -0
  86. data/spec/dummy/config/database.yml +25 -0
  87. data/spec/dummy/config/environment.rb +5 -0
  88. data/spec/dummy/config/environments/development.rb +31 -0
  89. data/spec/dummy/config/environments/production.rb +80 -0
  90. data/spec/dummy/config/environments/test.rb +36 -0
  91. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  92. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  93. data/spec/dummy/config/initializers/inflections.rb +16 -0
  94. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  95. data/spec/dummy/config/initializers/report_cat.rb +15 -0
  96. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  97. data/spec/dummy/config/initializers/session_store.rb +3 -0
  98. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  99. data/spec/dummy/config/locales/en.yml +38 -0
  100. data/spec/dummy/config/routes.rb +13 -0
  101. data/spec/dummy/config.ru +4 -0
  102. data/spec/dummy/db/development.sqlite3 +0 -0
  103. data/spec/dummy/db/schema.rb +26 -0
  104. data/spec/dummy/db/test.sqlite3 +0 -0
  105. data/spec/dummy/log/development.log +61 -0
  106. data/spec/dummy/log/test.log +26478 -0
  107. data/spec/dummy/public/404.html +58 -0
  108. data/spec/dummy/public/422.html +58 -0
  109. data/spec/dummy/public/500.html +57 -0
  110. data/spec/dummy/public/favicon.ico +0 -0
  111. data/spec/helpers/report_cat/reports_helper_spec.rb +224 -0
  112. data/spec/lib/report_cat/config_spec.rb +96 -0
  113. data/spec/lib/report_cat/core/chart_spec.rb +67 -0
  114. data/spec/lib/report_cat/core/column_spec.rb +156 -0
  115. data/spec/lib/report_cat/core/param_spec.rb +95 -0
  116. data/spec/lib/report_cat/core/report_spec.rb +342 -0
  117. data/spec/lib/report_cat/engine_spec.rb +9 -0
  118. data/spec/lib/report_cat/matchers/have_chart_spec.rb +36 -0
  119. data/spec/lib/report_cat/matchers/have_column_spec.rb +30 -0
  120. data/spec/lib/report_cat/matchers/have_param_spec.rb +33 -0
  121. data/spec/lib/report_cat/reports/cohort_report_spec.rb +215 -0
  122. data/spec/lib/report_cat/reports/date_range_report_spec.rb +125 -0
  123. data/spec/lib/report_cat/version_spec.rb +11 -0
  124. data/spec/lib/report_cat_spec.rb +62 -0
  125. data/spec/lib/tasks/report_cat.rake_spec.rb +13 -0
  126. data/spec/models/report_cat/date_range_spec.rb +144 -0
  127. data/spec/rails_helper.rb +49 -0
  128. data/spec/spec_helper.rb +23 -0
  129. data/spec/support/setup_reports.rb +28 -0
  130. data/spec/views/report_cat/reports/index.html.erb_spec.rb +16 -0
  131. data/spec/views/report_cat/reports/show.html.erb_spec.rb +19 -0
  132. metadata +489 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bb5df8435727e3a79d5e7fb487a3cb59dc51ee05
4
+ data.tar.gz: 17cbef82f87e179e82f53d7518a8452eab81879d
5
+ SHA512:
6
+ metadata.gz: 832119ce0f907982113db1b246dc796ad1ad0b7efbbcdfd0b94a8ab106e135508f34e8381c66534550927ccab13298ba5e53ef92d224c7dd46538fff80f9ed50
7
+ data.tar.gz: 10ef1443c4601eaa121cf6af0e5dd8348ed72f26eeeb43c4553eb262929a0a83f693827b280ce2e33aa904787576820a33b51d0ec339660f10a2ce76471b53ff
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ [![Build Status](https://travis-ci.org/schrodingersbox/report_cat.svg?branch=master)](https://travis-ci.org/schrodingersbox/report_cat)
2
+ [![Coverage Status](https://coveralls.io/repos/schrodingersbox/report_cat/badge.png?branch=master)](https://coveralls.io/r/schrodingersbox/report_cat?branch=master)
3
+ [![Code Climate](https://codeclimate.com/github/schrodingersbox/report_cat.png)](https://codeclimate.com/github/schrodingersbox/report_cat)
4
+ [![Dependency Status](https://gemnasium.com/schrodingersbox/report_cat.png)](https://gemnasium.com/schrodingersbox/report_cat)
5
+ [![Gem Version](https://badge.fury.io/rb/report_cat.png)](http://badge.fury.io/rb/report_cat)
6
+
7
+ # schrodingersbox/report_cat README
8
+
9
+ A Rails engine to generate simple web-based reports with charts along with Rspec matchers for testing them.
10
+
11
+ It currently supports:
12
+
13
+ * Simple reports
14
+ * Date range reports
15
+ * Date range cohort reports
16
+
17
+ It provides the following matchers:
18
+
19
+ * have_chart
20
+ * have_column
21
+ * have_param
22
+
23
+ Report subclasses will automatically appear under the ReportCat index controller,
24
+ allowing you to add a new report with custom form, columns and charts by just adding a single ReportCat::Report subclass.
25
+
26
+ ## Getting Started
27
+
28
+ 1. Add this to your `Gemfile` and `bundle install`
29
+
30
+ gem 'report_cat', :git => 'https://github.com/schrodingersbox/report_cat.git'
31
+
32
+ 2. Add this to your `config/routes.rb`
33
+
34
+ mount ReportCat::Engine => '/report_cat'
35
+
36
+ 3. Install and run migrations
37
+
38
+ rake report_cat:install:migrations
39
+ rake db:migrate
40
+
41
+ 4. Restart your Rails server
42
+
43
+ 5. Visit http://yourapp/report_cat in a browser
44
+
45
+ ## Background
46
+
47
+ _TODO: UML goes here_
48
+
49
+ ### Report Types
50
+
51
+
52
+
53
+ ### Building a report
54
+
55
+
56
+
57
+ ### Adding Params
58
+
59
+ add_param( name, type, value = nil, options = {} )
60
+
61
+ types = :check_box, :date, :select, :text_field
62
+ options = :hidden, :values
63
+
64
+ ### Adding Columns
65
+
66
+ add_column( name, type, options = {} )
67
+
68
+ types = :date, :float, :integer, :moving_average, :ratio, :report, :string
69
+ options = :hidden, :sql
70
+
71
+ ### Adding Charts
72
+
73
+ add_chart( name, type, label, values, options = {} )
74
+
75
+ types = :area, :bar, :column, :line, :pie
76
+
77
+ ## How To
78
+
79
+ ### Add New Reports
80
+
81
+ You can place new reports anywhere you like, but `app/reports` is the recommended location.
82
+
83
+ 1. Add the following to `config/application.rb`
84
+
85
+ Dir[Rails.root + 'app/reports/**/*.rb'].each { |path| require path }
86
+
87
+ 2. Create a subclass of `ReportCat::Core::Report`, `ReportCat::Report::DateRangeReport` or `ReportCat::Report::CohortReport`
88
+
89
+ class MyReport << ReportCat::Report::DateRangeReport
90
+
91
+ def initialize
92
+ super( :name => :my_report, :from => :users, :order_by => 'users.id asc' )
93
+ add_column( :total, :integer, :sql => 'count( users.id )' )
94
+ add_chart( :chart, :line, :start_date, :total )
95
+ end
96
+ end
97
+
98
+ 3. Or build one on the fly
99
+
100
+ report = ReportCat::Core::DateRangeReport.new( :name => :my_report, :from => :users, :order_by => 'users.id asc' )
101
+ report.add_column( :total, :integer, :sql => 'count( users.id )' )
102
+ report.add_chart( :chart, :line, :start_date, :total )
103
+ report.generate
104
+ report.rows.each { |row| puts "Total = #{row[0]} }
105
+
106
+
107
+ ### Reload Reports In Development Mode
108
+
109
+ Add the following to ApplicationController:
110
+
111
+ before_filter :require_reports if Rails.env.development?
112
+
113
+ def require_reports
114
+ silence_warnings do
115
+ Dir[Rails.root + 'app/reports/**/*.rb'].each { |path| require_dependency path }
116
+ end
117
+ end
118
+
119
+ NOTE: This is a rank hack and causes some weird behavior if you change a report's base class or override methods
120
+ without restarting the server. Please let me know if you find a better way to dynamically force reload in development.
121
+
122
+ ## Reference
123
+
124
+ * [Getting Started with Engines](http://edgeguides.rubyonrails.org/engines.html)
125
+ * [Testing Rails Engines With Rspec](http://whilefalse.net/2012/01/25/testing-rails-engines-rspec/)
126
+ * [How do I write a Rails 3.1 engine controller test in rspec?](http://stackoverflow.com/questions/5200654/how-do-i-write-a-rails-3-1-engine-controller-test-in-rspec)
127
+ * [Best practice for specifying dependencies that cannot be put in gemspec?](https://groups.google.com/forum/?fromgroups=#!topic/ruby-bundler/U7FMRAl3nJE)
128
+ * [Clarifying the Roles of the .gemspec and Gemfile](http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/)
129
+ * [The Semi-Isolated Rails Engine](http://bibwild.wordpress.com/2012/05/10/the-semi-isolated-rails-engine/)
130
+ * [Shoulda](https://github.com/thoughtbot/shoulda-matchers)
131
+
132
+ # TODO
133
+
134
+ * Ability to register "anonymous" reports of generic Report classes with factory
135
+
136
+ * Fix pending spec due to Travis problem
137
+
138
+ * Add a AnnotatedReport class - base report left joined to an "annotation" report
139
+
140
+ * Replace Google Charts with D3
141
+ * Improve Column modelling WRT calculated ratios and moving averages
142
+
143
+ * Document
144
+ * How To
145
+ * Background
146
+ * UML
147
+
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ReportCat'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ load 'tasks/spec_cat.rake'
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,43 @@
1
+ module ReportCat
2
+ class ReportsController < ApplicationController
3
+
4
+ layout :set_layout
5
+
6
+ before_filter :_authenticate!
7
+ before_filter :_authorize!
8
+ before_filter :set_reports
9
+
10
+ def index
11
+ end
12
+
13
+ def show
14
+ @report = @reports[ params[ :id ] ]
15
+ @report.back = params[ :back ]
16
+ @report.generate( params )
17
+
18
+ respond_to do |format|
19
+ format.html
20
+ format.csv { render :text => @report.to_csv, :content_type => 'text/csv' }
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def set_reports
27
+ @reports = ReportCat.reports
28
+ end
29
+
30
+ def set_layout
31
+ return ReportCat.config.layout
32
+ end
33
+
34
+ def _authenticate!
35
+ instance_eval( &ReportCat.config.authenticate_with )
36
+ end
37
+
38
+ def _authorize!
39
+ instance_eval( &ReportCat.config.authorize_with )
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,132 @@
1
+ module ReportCat
2
+ module ReportsHelper
3
+
4
+ def report_back( report )
5
+ if report.back
6
+ report_link( report.back )
7
+ else
8
+ link_to report_title, root_path
9
+ end
10
+ end
11
+
12
+ def report_chart_name( report, chart )
13
+ t( chart.name.to_sym, :scope => [ :report_cat, :charts ] )
14
+ end
15
+
16
+
17
+ def report_chart_partial
18
+ render :partial => 'report_cat/reports/google_charts'
19
+ end
20
+
21
+ def report_charts( report )
22
+ output = ''
23
+
24
+ report.charts.each do |chart|
25
+ output += content_tag( :div, '', :class => :chart,
26
+ :name => report_chart_name( report, chart ),
27
+ :chart => chart.type,
28
+ :columns => chart.columns( report ),
29
+ :data => chart.data( report ),
30
+ :options => chart.options.to_json )
31
+ end
32
+
33
+ output
34
+ end
35
+
36
+ def report_count( report )
37
+ t( :count, :scope => :report_cat, :count => report.rows.count )
38
+ end
39
+
40
+ def report_csv_link( report )
41
+ link_to t( :export_as_csv, :scope => :report_cat ), :params => params.merge( :format => :csv )
42
+ end
43
+
44
+ def report_description( report )
45
+ t( :description, :scope => [ :report_cat, :instances, report.name.to_sym ] )
46
+ end
47
+
48
+ def report_form( report )
49
+ form_tag report_path( report.name ), :method => :get do
50
+ @report.params.each do |param|
51
+ concat report_form_param( param )
52
+ end
53
+ concat submit_tag( t( :report, :scope => :report_cat ) )
54
+ end
55
+ end
56
+
57
+ def report_form_param( param )
58
+ name = t( param.name, :scope => [ :report_cat, :params ] )
59
+ if param.options[ :hidden ]
60
+ return report_param( param )
61
+ else
62
+ return content_tag( :div, label_tag( name ) + report_param( param ) )
63
+ end
64
+ end
65
+
66
+ def report_link( attributes )
67
+ attributes = attributes.dup
68
+ attributes[ :id ] = name = attributes.delete( :name )
69
+ name = t( :name, :scope => [ :report_cat, :instances, name ] )
70
+ path = defined?( report_cat ) ? report_cat.report_path( attributes ) : report_path( attributes )
71
+ link_to name, path
72
+ end
73
+
74
+ def report_list( reports )
75
+ content_tag( :ul ) do
76
+ reports.values.sort { |a,b| a.name <=> b.name }.each do |report|
77
+ unless ReportCat.config.excludes.include?( report.name.to_sym )
78
+ attributes = { :id => report.name }
79
+ path = defined?( report_cat ) ? report_cat.report_path( attributes ) : report_path( attributes )
80
+ link = link_to( report_name( report ), path)
81
+ concat content_tag( :li, link + ' - ' + report_description( report ) )
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def report_name( report )
88
+ t( :name, :scope => [ :report_cat, :instances, report.name.to_sym ] )
89
+ end
90
+
91
+ def report_param( param )
92
+ return hidden_field_tag( param.name, param.value ) if param.options[ :hidden ]
93
+
94
+ case param.type
95
+ when :check_box then return check_box_tag( param.name, '1', param.value )
96
+ when :date then return select_date( param.value, :prefix => param.name )
97
+ when :select then return select_tag( param.name, options_for_select( param.options[ :values ], param.value ) )
98
+ when :text_field then return text_field_tag( param.name, param.value )
99
+ else
100
+ raise "Unknown param: #{param.type}"
101
+ end
102
+ end
103
+
104
+ def report_table( report )
105
+ content_tag( :table, :border => 1 ) do
106
+ output = content_tag( :tr ) do
107
+ report.columns.each do |column|
108
+ concat content_tag( :th, column.name.to_s ) unless column.options[ :hidden ]
109
+ end
110
+ end
111
+
112
+ report.rows.each do |row|
113
+ output += content_tag( :tr ) do
114
+ row.each_index do |index|
115
+ column = report.columns[ index ]
116
+ data = row[ index ]
117
+ data = report_link( data ) if ( column.type == :report )
118
+ concat content_tag( :td, data ) unless column.options[ :hidden ]
119
+ end
120
+ end
121
+ end
122
+
123
+ output
124
+ end
125
+ end
126
+
127
+ def report_title
128
+ t :reports, :scope => :report_cat
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,94 @@
1
+ module ReportCat
2
+ class DateRange < ActiveRecord::Base
3
+
4
+ def self.range( period, start_date, stop_date )
5
+ sql = [
6
+ sql_intersect( start_date, stop_date ),
7
+ sql_period( period )
8
+ ].join( ' and ' )
9
+
10
+ DateRange.where( sql ).order( "#{table_name}.start_date asc" )
11
+ end
12
+
13
+ #############################################################################
14
+ # ::generate
15
+
16
+ def self.generate( period, start_date, stop_date )
17
+ date_ranges = {}
18
+ range( period, start_date, stop_date ).each { |d| date_ranges[ d.start_date ] = true }
19
+
20
+ iterate( period, start_date, stop_date ) do |start_date, stop_date|
21
+ unless date_ranges[ start_date ]
22
+ DateRange.create( :period => period, :start_date => start_date, :stop_date => stop_date )
23
+ end
24
+ end
25
+ end
26
+
27
+ #############################################################################
28
+ # ::iterate
29
+
30
+ def self.iterate( period, start_date, stop_date )
31
+ start_date = Date.parse( start_date ) if start_date.is_a?( String )
32
+ stop_date = Date.parse( stop_date ) if stop_date.is_a?( String )
33
+
34
+ while( start_date <= stop_date )
35
+ case period
36
+ when :daily
37
+ next_date = start_date
38
+ when :weekly
39
+ start_date = start_date.beginning_of_week
40
+ next_date = start_date.end_of_week
41
+ when :monthly
42
+ start_date = start_date.beginning_of_month
43
+ next_date = start_date.end_of_month
44
+ when :quarterly
45
+ start_date = start_date.beginning_of_quarter
46
+ next_date = start_date.end_of_quarter
47
+ when :yearly
48
+ start_date = start_date.beginning_of_year
49
+ next_date = start_date.end_of_year
50
+ else
51
+ raise "Unknown date range: #{period}"
52
+ end
53
+
54
+ yield start_date, next_date
55
+
56
+ start_date = next_date + 1
57
+ end
58
+ end
59
+
60
+ #############################################################################
61
+ # ::join_*
62
+
63
+ def self.join_to( table, column )
64
+ "join #{table} on date( #{table}.#{column} ) between #{table_name}.start_date and #{table_name}.stop_date"
65
+ end
66
+
67
+ def self.join_before( table, column )
68
+ "join #{table} on date( #{table}.#{column} ) <= #{table_name}.stop_date"
69
+ end
70
+
71
+ def self.join_after( table, column )
72
+ "join #{table} on date( #{table}.#{column} ) > #{table_name}.stop_date"
73
+ end
74
+
75
+ #############################################################################
76
+ # ::sql_*
77
+
78
+ def self.sql_intersect( start_date, stop_date )
79
+ sql =<<-EOSQL
80
+ (
81
+ #{table_name}.start_date between '#{start_date}' and '#{stop_date}'
82
+ or
83
+ '#{start_date}' between #{table_name}.start_date and #{table_name}.stop_date
84
+ )
85
+ EOSQL
86
+ end
87
+
88
+ def self.sql_period( period )
89
+ "#{table_name}.period = '#{period}'"
90
+ end
91
+
92
+ end
93
+ end
94
+
@@ -0,0 +1,61 @@
1
+ <%= javascript_include_tag 'https://www.google.com/jsapi' %>
2
+ <%= javascript_include_tag 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js' %>
3
+
4
+ <script type="text/javascript">
5
+ google.load('visualization', '1', {'packages':['corechart']});
6
+ google.setOnLoadCallback(drawCharts);
7
+
8
+ function makeDataTable( element ) {
9
+ var columns = eval( element.getAttribute( 'columns' ) );
10
+ var data = eval( element.getAttribute( 'data' ) );
11
+ var dataTable = new google.visualization.DataTable();
12
+
13
+ for( i = 0; i < columns.length; i++ ) {
14
+ dataTable.addColumn( columns[ i ][ 0 ], columns[ i ][ 1 ] );
15
+ }
16
+
17
+ dataTable.addRows( data );
18
+
19
+ return dataTable;
20
+ }
21
+
22
+ function makeChart( index, element ) {
23
+ var dataTable = makeDataTable( element );
24
+ var customOptions = eval( element.getAttribute( 'options' ) );
25
+ var defaultOptions = { is3D: true, title: element.getAttribute( 'name' ) };
26
+ var chart = undefined;
27
+
28
+ switch( element.getAttribute( 'chart' ) ) {
29
+ case 'area':
30
+ chart = new google.visualization.AreaChart( element );
31
+ break;
32
+
33
+ case 'bar':
34
+ chart = new google.visualization.BarChart( element );
35
+ break;
36
+
37
+ case 'column':
38
+ chart = new google.visualization.ColumnChart( element );
39
+ break;
40
+
41
+ case 'line':
42
+ chart = new google.visualization.LineChart( element );
43
+ break;
44
+
45
+ case 'pie':
46
+ chart = new google.visualization.PieChart( element );
47
+ break;
48
+ }
49
+
50
+ if ( chart != undefined ) {
51
+ jQuery.extend( true, defaultOptions, customOptions );
52
+ chart.draw( dataTable, defaultOptions );
53
+ }
54
+ }
55
+
56
+ function drawCharts() {
57
+ $('[chart]').each( makeChart );
58
+ }
59
+
60
+
61
+ </script>
@@ -0,0 +1,2 @@
1
+ <h1><%= report_title %></h1>
2
+ <%= report_list( @reports ) %>
@@ -0,0 +1,15 @@
1
+ <p><%= report_back( @report ) %></p>
2
+
3
+ <h1><%= report_name( @report ) %></h1>
4
+ <%= report_description( @report ) %>
5
+
6
+ <p><%= report_form( @report ) %></p>
7
+
8
+ <p><%= raw report_charts( @report ) %></p>
9
+ <p><%= report_chart_partial %></p>
10
+
11
+ <p><%= report_count( @report ) %></p>
12
+ <p><%= report_table( @report ) %></p>
13
+ <p><%= report_csv_link( @report ) %></p>
14
+
15
+
@@ -0,0 +1,26 @@
1
+ en:
2
+ report_cat:
3
+ back: Back
4
+ count: "%{count} rows"
5
+ export_as_csv: Export as CSV
6
+ report: Report
7
+ reports: Reports
8
+ charts:
9
+ cohort_line: Cohort Line Chart
10
+ instances:
11
+ cohort_report:
12
+ name: Cohort Report
13
+ description: Generic cohort reporting
14
+ date_range_report:
15
+ name: Date Range Report
16
+ description: Generic daily, weekly, monthly, quarterly, yearly reports
17
+ params:
18
+ activated: Activated
19
+ date: Date
20
+ group: Group
21
+ group_by: Group By
22
+ is_cohort: Is Cohort
23
+ mode: Mode
24
+ period: Period
25
+ start_date: Start Date
26
+ stop_date: Stop Date
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ ReportCat::Engine.routes.draw do
2
+
3
+ root :to => 'reports#index'
4
+
5
+ resources :reports, :only => [ :index, :show ]
6
+
7
+ end
@@ -0,0 +1,13 @@
1
+ class CreateDateRanges < ActiveRecord::Migration
2
+ def change
3
+ create_table :report_cat_date_ranges do |t|
4
+ t.date :start_date
5
+ t.date :stop_date
6
+ t.string :period
7
+
8
+ t.index [ :period, :start_date ], :unique => true
9
+ t.index [ :period, :stop_date ], :unique => true
10
+ t.index [ :period, :start_date, :stop_date ], :unique => true, :name => 'index_report_cat_date_ranges_on_period_and_dates'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ require 'singleton'
2
+
3
+ module ReportCat
4
+
5
+ class Config
6
+ include Singleton
7
+
8
+ NIL_PROC = proc {}
9
+
10
+ attr_accessor :authenticate, :authorize
11
+ attr_accessor :layout
12
+ attr_accessor :excludes
13
+
14
+ def initialize
15
+ @excludes = []
16
+ end
17
+
18
+ def authenticate_with(&blk)
19
+ @authenticate = blk if blk
20
+ @authenticate || NIL_PROC
21
+ end
22
+
23
+ def authorize_with(&block)
24
+ @authorize = block if block
25
+ @authorize || NIL_PROC
26
+ end
27
+
28
+ end
29
+
30
+ end