daily 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.mdown +46 -0
- data/Rakefile +7 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/javascripts/application.js +9 -0
- data/app/assets/stylesheets/application.css +16 -0
- data/app/assets/stylesheets/bootstrap.css +2467 -0
- data/app/controllers/application_controller.rb +14 -0
- data/app/controllers/files_controller.rb +5 -0
- data/app/controllers/main_controller.rb +5 -0
- data/app/controllers/reports_controller.rb +35 -0
- data/app/controllers/tables_controller.rb +20 -0
- data/app/controllers/users_controller.rb +28 -0
- data/app/formatters/html_formatter.rb +65 -0
- data/app/formatters/json_formatter.rb +81 -0
- data/app/helpers/application_helper.rb +85 -0
- data/app/jobs/generate_report_job.rb +13 -0
- data/app/models/report.rb +112 -0
- data/app/models/table.rb +65 -0
- data/app/models/user.rb +20 -0
- data/app/transforms/column_filter.rb +7 -0
- data/app/transforms/moving_average.rb +119 -0
- data/app/transforms/transform.rb +32 -0
- data/app/views/devise/sessions/new.erb +15 -0
- data/app/views/layouts/application.html.erb +40 -0
- data/app/views/main/home.erb +4 -0
- data/app/views/reports/_form.erb +14 -0
- data/app/views/reports/_list.erb +9 -0
- data/app/views/reports/edit.erb +2 -0
- data/app/views/reports/index.erb +3 -0
- data/app/views/reports/new.erb +2 -0
- data/app/views/reports/show.erb +23 -0
- data/app/views/tables/_form.erb +8 -0
- data/app/views/tables/_list.erb +9 -0
- data/app/views/tables/edit.erb +2 -0
- data/app/views/tables/index.erb +3 -0
- data/app/views/tables/new.erb +2 -0
- data/app/views/tables/show.erb +35 -0
- data/app/views/users/_form.erb +5 -0
- data/app/views/users/_items.erb +8 -0
- data/app/views/users/_list.erb +9 -0
- data/app/views/users/edit.erb +2 -0
- data/app/views/users/index.erb +3 -0
- data/app/views/users/new.erb +2 -0
- data/app/views/users/show.erb +4 -0
- data/config/application.rb +52 -0
- data/config/authorization_rules.rb +49 -0
- data/config/boot.rb +6 -0
- data/config/daily.example.yml +19 -0
- data/config/daily.yml +27 -0
- data/config/database.yml +25 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +32 -0
- data/config/environments/production.rb +63 -0
- data/config/environments/test.rb +39 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/devise.rb +212 -0
- data/config/initializers/inflections.rb +10 -0
- data/config/initializers/mime_types.rb +5 -0
- data/config/initializers/ruport.rb +5 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/initializers/simple_form.rb +113 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/devise.en.yml +58 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/simple_form.en.yml +24 -0
- data/config/routes.rb +29 -0
- data/db/development.sqlite3 +0 -0
- data/db/migrate/20111109081414_devise_create_users.rb +28 -0
- data/db/migrate/20111111165640_create_tables.rb +14 -0
- data/db/migrate/20111112022333_add_name_to_tables.rb +9 -0
- data/db/migrate/20111112170802_add_reports.rb +15 -0
- data/db/migrate/20111113000026_add_guid_to_tables.rb +9 -0
- data/db/migrate/20111113073326_add_times_to_reports.rb +11 -0
- data/db/migrate/20111113075747_add_times_to_tables.rb +9 -0
- data/db/migrate/20111114041729_create_delayed_jobs.rb +21 -0
- data/db/migrate/20111114053016_add_report_to_delayed_jobs.rb +11 -0
- data/db/migrate/20111115014959_add_admin_to_users.rb +9 -0
- data/db/migrate/20111127065357_add_column_names_to_table.rb +9 -0
- data/db/migrate/20111203020425_add_transform_to_table.rb +16 -0
- data/db/migrate/20111214020029_add_formatter_data_to_reports.rb +8 -0
- data/db/schema.rb +80 -0
- data/db/seeds.rb +7 -0
- data/db/test.sqlite3 +0 -0
- data/lib/daily.rb +9 -0
- data/lib/daily/daily_config.rb +51 -0
- data/lib/daily/engine.rb +4 -0
- data/lib/daily/has_data.rb +52 -0
- data/lib/daily/shared_behaviors.rb +65 -0
- data/lib/daily/version.rb +3 -0
- data/lib/tasks/user.rake +24 -0
- metadata +454 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
class ApplicationController < ActionController::Base
|
2
|
+
protect_from_forgery
|
3
|
+
before_filter :set_current_user
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def set_current_user
|
8
|
+
Authorization.current_user = current_user
|
9
|
+
end
|
10
|
+
|
11
|
+
def permission_denied
|
12
|
+
render :file => "public/404.html", :status => 404, :layout => false
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ReportsController < InheritedResources::Base
|
2
|
+
nested_belongs_to :table, :optional => true
|
3
|
+
filter_resource_access :nested_in => :table, :additional_member => :generate
|
4
|
+
|
5
|
+
def create
|
6
|
+
build_resource.user = current_user
|
7
|
+
create! do |success, failure|
|
8
|
+
success.all { redirect_to table_report_path(@report.table, @report)}
|
9
|
+
failure.all { render :new }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
update! do |success, failure|
|
15
|
+
success.all { redirect_to table_report_path(@report.table, @report)}
|
16
|
+
failure.all { render :edit }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate
|
21
|
+
@report.queue_now!
|
22
|
+
redirect_to table_report_path(@report.table, @report)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# methods to make declarative_authorization allow optional table
|
28
|
+
def load_table
|
29
|
+
@table ||= load_parent_controller_object(:table) if params[:table_id]
|
30
|
+
@table # loaded by inherited_resources if there
|
31
|
+
end
|
32
|
+
def new_report_for_collection
|
33
|
+
@report ||= @table ? new_controller_object_for_collection : Report.new
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class TablesController < InheritedResources::Base
|
2
|
+
filter_resource_access
|
3
|
+
|
4
|
+
def create
|
5
|
+
build_resource.user = current_user
|
6
|
+
build_resource.data_type = "sql"
|
7
|
+
create!
|
8
|
+
end
|
9
|
+
|
10
|
+
def update
|
11
|
+
update!
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
show! do
|
16
|
+
@test_html = @table.test if params[:test]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class UsersController < InheritedResources::Base
|
2
|
+
before_filter :user_from_current, :only => :edit
|
3
|
+
filter_resource_access
|
4
|
+
|
5
|
+
def update
|
6
|
+
params[:user][:password] = nil if params[:user][:password].blank?
|
7
|
+
|
8
|
+
update! do |success, failure|
|
9
|
+
success.all { redirect_after_update }
|
10
|
+
failure.all { render :edit }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
def user_from_current
|
16
|
+
params[:id] ||= (current_user.try(:id) || 0)
|
17
|
+
end
|
18
|
+
def redirect_after_update
|
19
|
+
if @user == current_user
|
20
|
+
sign_in @user, :bypass => true
|
21
|
+
redirect_to user_root_path
|
22
|
+
elsif permitted_to? :show, @user
|
23
|
+
redirect_to user_path(@user)
|
24
|
+
else
|
25
|
+
redirect_to user_root_path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# monkey patching HTML formatter to be safe
|
2
|
+
|
3
|
+
module Ruport
|
4
|
+
class Formatter::HTML < Formatter
|
5
|
+
|
6
|
+
def safe(val)
|
7
|
+
out = ERB::Util.html_escape(val.to_s)
|
8
|
+
out.gsub("&nbsp;", " ")
|
9
|
+
end
|
10
|
+
|
11
|
+
def safe_join(array, sep)
|
12
|
+
safe_array = array.collect{ |a| safe(a) }
|
13
|
+
safe_array.join(sep)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generates table headers based on the column names of your Data::Table.
|
17
|
+
#
|
18
|
+
# This method does not do anything if options.show_table_headers is false
|
19
|
+
# or the Data::Table has no column names.
|
20
|
+
def build_table_header
|
21
|
+
output << "\t<table>\n"
|
22
|
+
unless data.column_names.empty? || !options.show_table_headers
|
23
|
+
output << "\t\t<tr>\n\t\t\t<th>" +
|
24
|
+
safe_join(data.column_names, "</th>\n\t\t\t<th>") +
|
25
|
+
"</th>\n\t\t</tr>\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Renders individual rows for the table.
|
30
|
+
def build_row(data = self.data)
|
31
|
+
output <<
|
32
|
+
"\t\t<tr>\n\t\t\t<td>" +
|
33
|
+
safe_join(data.to_a, "</td>\n\t\t\t<td>") +
|
34
|
+
"</td>\n\t\t</tr>\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Renders the header for a group using the group name.
|
38
|
+
#
|
39
|
+
def build_group_header
|
40
|
+
output << "\t<p>#{safe(data.name)}</p>\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def render_justified_grouping
|
46
|
+
output << "\t<table>\n\t\t<tr>\n\t\t\t<th>" +
|
47
|
+
"#{safe(data.grouped_by)}</th>\n\t\t\t<th>" +
|
48
|
+
safe_join(grouping_columns, "</th>\n\t\t\t<th>") +
|
49
|
+
"</th>\n\t\t</tr>\n"
|
50
|
+
data.each do |name, group|
|
51
|
+
group.each_with_index do |row, i|
|
52
|
+
output << "\t\t<tr>\n\t\t\t"
|
53
|
+
if i == 0
|
54
|
+
output << "<td class=\"groupName\">#{safe(name)}</td>\n\t\t\t<td>"
|
55
|
+
else
|
56
|
+
output << "<td> </td>\n\t\t\t<td>"
|
57
|
+
end
|
58
|
+
output << safe_join(row.to_a, "</td>\n\t\t\t<td>") +
|
59
|
+
"</td>\n\t\t</tr>\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
output << "\t</table>\n"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Formatter::JSON < Formatter
|
3
|
+
renders :json, :for => [ Ruport::Controller::Row, Ruport::Controller::Table,
|
4
|
+
Ruport::Controller::Group, Ruport::Controller::Grouping ]
|
5
|
+
|
6
|
+
# Hook for setting available options using a template. See the template
|
7
|
+
# documentation for the available options and their format.
|
8
|
+
def apply_template
|
9
|
+
apply_table_format_template(template.table)
|
10
|
+
apply_grouping_format_template(template.grouping)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Start the JSON
|
14
|
+
#
|
15
|
+
def build_table_header
|
16
|
+
output << "[\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Uses the Row controller to build up the table body.
|
20
|
+
#
|
21
|
+
def build_table_body
|
22
|
+
data.each_with_index do |row, i|
|
23
|
+
output << ",\n" if i > 0
|
24
|
+
build_row(row)
|
25
|
+
end
|
26
|
+
output << "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
# End the JSON
|
30
|
+
def build_table_footer
|
31
|
+
output << "]"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Renders individual rows for the table.
|
35
|
+
#
|
36
|
+
def build_row(data = self.data)
|
37
|
+
values = data.to_a
|
38
|
+
keys = self.data.column_names.to_a
|
39
|
+
hash = {}
|
40
|
+
values.each_with_index do |val, i|
|
41
|
+
key = (keys[i] || i).to_s
|
42
|
+
hash[key] = val
|
43
|
+
end
|
44
|
+
line = hash.to_json.to_s
|
45
|
+
output << " #{line}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Renders the header for a group using the group name.
|
49
|
+
#
|
50
|
+
def build_group_header
|
51
|
+
output << " \"#{data.name}\":"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates the group body. Since group data is a table, just uses the
|
55
|
+
# Table controller.
|
56
|
+
#
|
57
|
+
def build_group_body
|
58
|
+
render_table data, options.to_hash
|
59
|
+
end
|
60
|
+
|
61
|
+
# Generates the body for a grouping. Iterates through the groups and
|
62
|
+
# renders them using the group controller.
|
63
|
+
#
|
64
|
+
def build_grouping_body
|
65
|
+
arr = []
|
66
|
+
data.each do |_,group|
|
67
|
+
arr << render_group(group, options)
|
68
|
+
end
|
69
|
+
output << arr.join(",\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def apply_table_format_template(t)
|
75
|
+
end
|
76
|
+
|
77
|
+
def apply_grouping_format_template(t)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ApplicationHelper
|
2
|
+
def title(text)
|
3
|
+
@title = text
|
4
|
+
end
|
5
|
+
|
6
|
+
def page_title
|
7
|
+
@title || "Daily"
|
8
|
+
end
|
9
|
+
|
10
|
+
def header_title
|
11
|
+
@title
|
12
|
+
end
|
13
|
+
|
14
|
+
def seconds_sentence(time)
|
15
|
+
return "Running now..." if time < 0
|
16
|
+
return "Less than a second" if time == 0
|
17
|
+
return "1 second" if time == 1
|
18
|
+
"#{time} seconds"
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_time(time)
|
22
|
+
seconds = Time.now.to_i - time.to_i
|
23
|
+
|
24
|
+
if seconds.abs < 10
|
25
|
+
out = "Now"
|
26
|
+
elsif seconds > 0
|
27
|
+
out = "#{time_ago_in_words(time)} ago"
|
28
|
+
else
|
29
|
+
out = "#{time_ago_in_words(time)} from now"
|
30
|
+
end
|
31
|
+
out += " (#{time.strftime("%T %Z")})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def report_time_run(report)
|
35
|
+
return "Unknown" if report.generate_started_at.nil?
|
36
|
+
return "Unknown" if report.generate_ended_at.nil?
|
37
|
+
|
38
|
+
time = report.generate_ended_at.to_i - report.generate_started_at.to_i
|
39
|
+
seconds_sentence(time)
|
40
|
+
end
|
41
|
+
|
42
|
+
def report_time_next(report)
|
43
|
+
job = report.next_job
|
44
|
+
return "Not scheduled" if job.nil?
|
45
|
+
return "Running now..." if job.locked_at.present?
|
46
|
+
display_time(job.run_at)
|
47
|
+
end
|
48
|
+
|
49
|
+
def report_time_ago(report)
|
50
|
+
return "None" unless report.file_exists?
|
51
|
+
return "Unknown" if report.generate_ended_at.nil?
|
52
|
+
display_time(report.generate_ended_at)
|
53
|
+
end
|
54
|
+
|
55
|
+
def table_time_run(table)
|
56
|
+
return "Unknown" if table.fetch_time_in_seconds.nil?
|
57
|
+
seconds_sentence(table.fetch_time_in_seconds)
|
58
|
+
end
|
59
|
+
|
60
|
+
def report_error_html(report)
|
61
|
+
report.jobs.each do |job|
|
62
|
+
if job.last_error
|
63
|
+
msg = job.last_error.gsub("\\n", "\n")
|
64
|
+
msg = msg.split("\n").first unless Rails.env.development?
|
65
|
+
return simple_format(msg)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
""
|
69
|
+
end
|
70
|
+
|
71
|
+
def formatter_display(object)
|
72
|
+
out = "<hr/>".html_safe
|
73
|
+
out += content_tag(:p, "Formatter: " + object.formatter.to_s)
|
74
|
+
out += simple_format(h(JSON.pretty_generate(object.formatter_data)).gsub(" ", " ")) unless object.formatter_data.blank?
|
75
|
+
out.html_safe
|
76
|
+
end
|
77
|
+
|
78
|
+
def transform_display(object)
|
79
|
+
return "" if object.transform.blank?
|
80
|
+
out = "<hr/>".html_safe
|
81
|
+
out += content_tag(:p, "Transform: " + object.transform.constantize.display_name)
|
82
|
+
out += simple_format(h(JSON.pretty_generate(object.transform_data)).gsub(" ", " ")) unless object.transform_data.blank?
|
83
|
+
out.html_safe
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class Report < ActiveRecord::Base
|
2
|
+
include SharedBehaviors
|
3
|
+
belongs_to :user
|
4
|
+
belongs_to :table
|
5
|
+
has_many :jobs, :class_name => "::Delayed::Job"
|
6
|
+
|
7
|
+
has_data :formatter
|
8
|
+
|
9
|
+
generate_guid :filename
|
10
|
+
|
11
|
+
validates_presence_of :user
|
12
|
+
validates_presence_of :table
|
13
|
+
validates_unique_presence_of :name
|
14
|
+
validates_stripped_presence_of :formatter
|
15
|
+
|
16
|
+
before_validation :filename_extension_update
|
17
|
+
|
18
|
+
after_save :ensure_job
|
19
|
+
after_save :queue_now!, :if => :filename_changed?
|
20
|
+
|
21
|
+
def self.formatters
|
22
|
+
Ruport::Controller::Table.formats.keys
|
23
|
+
end
|
24
|
+
|
25
|
+
def parent
|
26
|
+
"files/#{table.guid}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def localdir
|
30
|
+
"#{Rails.root}/#{parent}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def localfile
|
34
|
+
"#{localdir}/#{filename}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def url(root)
|
38
|
+
root += "/" unless root[-1, 1] == "/"
|
39
|
+
"#{root}#{parent}/#{filename}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def file_exists?
|
43
|
+
File.file?(localfile)
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate!
|
47
|
+
Dir.mkdir(localdir) unless File.directory?(localdir)
|
48
|
+
touch(:generate_started_at)
|
49
|
+
data = table.result
|
50
|
+
data = apply_transform(data)
|
51
|
+
val = save_as!(data, localfile)
|
52
|
+
touch(:generate_ended_at)
|
53
|
+
val
|
54
|
+
end
|
55
|
+
|
56
|
+
def save_as!(data, path)
|
57
|
+
tempfile = "#{Rails.root}/process/#{Time.now.to_i}_#{rand(9999999)}_#{rand(9999999)}.tmp"
|
58
|
+
return false unless data.as(formatter.to_sym, (formatter_data || {}).merge(:file => tempfile))
|
59
|
+
return false unless File.file?(tempfile)
|
60
|
+
File.rename(tempfile, path)
|
61
|
+
File.file?(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def queue_next!
|
65
|
+
job = GenerateReportJob.new(id, true)
|
66
|
+
Delayed::Job.enqueue :payload_object => job,
|
67
|
+
:priority => 10,
|
68
|
+
:run_at => calculate_next_gen_time,
|
69
|
+
:report_id => id
|
70
|
+
end
|
71
|
+
|
72
|
+
def queue_now!
|
73
|
+
job = GenerateReportJob.new(id, false)
|
74
|
+
Delayed::Job.enqueue :payload_object => job,
|
75
|
+
:priority => 0,
|
76
|
+
:report_id => id
|
77
|
+
end
|
78
|
+
|
79
|
+
def next_job
|
80
|
+
jobs.by_priority.first
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def guid_append
|
86
|
+
return "" if formatter.blank?
|
87
|
+
".#{formatter.strip}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def calculate_next_gen_time
|
91
|
+
Time.now + 1.hour
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def ensure_job
|
97
|
+
queue_next! if jobs.reload.size == 0
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def filename_extension_update
|
102
|
+
return true if filename_changed?
|
103
|
+
return true unless formatter_changed?
|
104
|
+
pieces = filename.split(".")
|
105
|
+
if pieces.size > 1
|
106
|
+
pieces[-1] = formatter.to_s
|
107
|
+
self.filename = pieces.join(".")
|
108
|
+
end
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|