report_cat 0.2.0

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