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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +147 -0
- data/Rakefile +24 -0
- data/app/assets/javascripts/report_cat/application.js +13 -0
- data/app/assets/stylesheets/report_cat/application.css +13 -0
- data/app/controllers/report_cat/reports_controller.rb +43 -0
- data/app/helpers/report_cat/reports_helper.rb +132 -0
- data/app/models/report_cat/date_range.rb +94 -0
- data/app/views/report_cat/reports/_google_charts.html.erb +61 -0
- data/app/views/report_cat/reports/index.html.erb +2 -0
- data/app/views/report_cat/reports/show.html.erb +15 -0
- data/config/locales/en.yml +26 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20130918075200_create_date_ranges.rb +13 -0
- data/lib/report_cat/config.rb +30 -0
- data/lib/report_cat/core/chart.rb +50 -0
- data/lib/report_cat/core/column.rb +70 -0
- data/lib/report_cat/core/param.rb +35 -0
- data/lib/report_cat/core/report.rb +127 -0
- data/lib/report_cat/engine.rb +11 -0
- data/lib/report_cat/matchers/have_chart.rb +58 -0
- data/lib/report_cat/matchers/have_column.rb +45 -0
- data/lib/report_cat/matchers/have_param.rb +52 -0
- data/lib/report_cat/reports/cohort_report.rb +113 -0
- data/lib/report_cat/reports/date_range_report.rb +66 -0
- data/lib/report_cat/version.rb +3 -0
- data/lib/report_cat.rb +39 -0
- data/lib/tasks/report_cat.rake +4 -0
- data/spec/controllers/report_cat/reports_controller_spec.rb +100 -0
- data/spec/coverage_spec.rb +18 -0
- data/spec/data/helpers/report_charts.html +1 -0
- data/spec/data/helpers/report_charts.html.tmp +1 -0
- data/spec/data/helpers/report_form.html +63 -0
- data/spec/data/helpers/report_form.html.tmp +63 -0
- data/spec/data/helpers/report_form_param.html +1 -0
- data/spec/data/helpers/report_form_param.html.tmp +1 -0
- data/spec/data/helpers/report_list.html +1 -0
- data/spec/data/helpers/report_list.html.tmp +1 -0
- data/spec/data/helpers/report_param_checkbox.html +1 -0
- data/spec/data/helpers/report_param_checkbox.html.tmp +1 -0
- data/spec/data/helpers/report_param_date.html +60 -0
- data/spec/data/helpers/report_param_date.html.tmp +60 -0
- data/spec/data/helpers/report_param_hidden.html +1 -0
- data/spec/data/helpers/report_param_hidden.html.tmp +1 -0
- data/spec/data/helpers/report_param_select.html +3 -0
- data/spec/data/helpers/report_param_select.html.tmp +3 -0
- data/spec/data/helpers/report_param_text_field.html +1 -0
- data/spec/data/helpers/report_param_text_field.html.tmp +1 -0
- data/spec/data/helpers/report_table.html +1 -0
- data/spec/data/helpers/report_table.html.tmp +1 -0
- data/spec/data/helpers/report_table_hidden.html +1 -0
- data/spec/data/helpers/report_table_hidden.html.tmp +1 -0
- data/spec/data/lib/chart_columns.json +1 -0
- data/spec/data/lib/chart_columns.json.tmp +1 -0
- data/spec/data/lib/chart_data.json +1 -0
- data/spec/data/lib/chart_data.json.tmp +1 -0
- data/spec/data/lib/date_range_report_where.sql +6 -0
- data/spec/data/lib/date_range_report_where.sql.tmp +6 -0
- data/spec/data/lib/report.csv +3 -0
- data/spec/data/lib/report.csv.tmp +3 -0
- data/spec/data/lib/report.sql +1 -0
- data/spec/data/lib/report.sql.tmp +1 -0
- data/spec/data/models/sql_intersect.sql +5 -0
- data/spec/data/models/sql_intersect.sql.tmp +5 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +21 -0
- data/spec/dummy/app/controllers/root_controller.rb +20 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +13 -0
- data/spec/dummy/app/models/visit.rb +16 -0
- data/spec/dummy/app/reports/retention_cohort_report.rb +19 -0
- data/spec/dummy/app/reports/retention_report.rb +30 -0
- data/spec/dummy/app/reports/user_report.rb +23 -0
- data/spec/dummy/app/views/layouts/admin.html.erb +19 -0
- data/spec/dummy/app/views/layouts/application.html.erb +19 -0
- data/spec/dummy/app/views/root/index.html.erb +8 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +31 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/report_cat.rb +15 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +38 -0
- data/spec/dummy/config/routes.rb +13 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +26 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +61 -0
- data/spec/dummy/log/test.log +26478 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/helpers/report_cat/reports_helper_spec.rb +224 -0
- data/spec/lib/report_cat/config_spec.rb +96 -0
- data/spec/lib/report_cat/core/chart_spec.rb +67 -0
- data/spec/lib/report_cat/core/column_spec.rb +156 -0
- data/spec/lib/report_cat/core/param_spec.rb +95 -0
- data/spec/lib/report_cat/core/report_spec.rb +342 -0
- data/spec/lib/report_cat/engine_spec.rb +9 -0
- data/spec/lib/report_cat/matchers/have_chart_spec.rb +36 -0
- data/spec/lib/report_cat/matchers/have_column_spec.rb +30 -0
- data/spec/lib/report_cat/matchers/have_param_spec.rb +33 -0
- data/spec/lib/report_cat/reports/cohort_report_spec.rb +215 -0
- data/spec/lib/report_cat/reports/date_range_report_spec.rb +125 -0
- data/spec/lib/report_cat/version_spec.rb +11 -0
- data/spec/lib/report_cat_spec.rb +62 -0
- data/spec/lib/tasks/report_cat.rake_spec.rb +13 -0
- data/spec/models/report_cat/date_range_spec.rb +144 -0
- data/spec/rails_helper.rb +49 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/setup_reports.rb +28 -0
- data/spec/views/report_cat/reports/index.html.erb_spec.rb +16 -0
- data/spec/views/report_cat/reports/show.html.erb_spec.rb +19 -0
- 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
|
+
[](https://travis-ci.org/schrodingersbox/report_cat)
|
|
2
|
+
[](https://coveralls.io/r/schrodingersbox/report_cat?branch=master)
|
|
3
|
+
[](https://codeclimate.com/github/schrodingersbox/report_cat)
|
|
4
|
+
[](https://gemnasium.com/schrodingersbox/report_cat)
|
|
5
|
+
[](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,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,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
|