daily 0.0.2
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.
- 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
|