report_cat 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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,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
|