daily 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.mdown +46 -0
  3. data/Rakefile +7 -0
  4. data/app/assets/images/rails.png +0 -0
  5. data/app/assets/javascripts/application.js +9 -0
  6. data/app/assets/stylesheets/application.css +16 -0
  7. data/app/assets/stylesheets/bootstrap.css +2467 -0
  8. data/app/controllers/application_controller.rb +14 -0
  9. data/app/controllers/files_controller.rb +5 -0
  10. data/app/controllers/main_controller.rb +5 -0
  11. data/app/controllers/reports_controller.rb +35 -0
  12. data/app/controllers/tables_controller.rb +20 -0
  13. data/app/controllers/users_controller.rb +28 -0
  14. data/app/formatters/html_formatter.rb +65 -0
  15. data/app/formatters/json_formatter.rb +81 -0
  16. data/app/helpers/application_helper.rb +85 -0
  17. data/app/jobs/generate_report_job.rb +13 -0
  18. data/app/models/report.rb +112 -0
  19. data/app/models/table.rb +65 -0
  20. data/app/models/user.rb +20 -0
  21. data/app/transforms/column_filter.rb +7 -0
  22. data/app/transforms/moving_average.rb +119 -0
  23. data/app/transforms/transform.rb +32 -0
  24. data/app/views/devise/sessions/new.erb +15 -0
  25. data/app/views/layouts/application.html.erb +40 -0
  26. data/app/views/main/home.erb +4 -0
  27. data/app/views/reports/_form.erb +14 -0
  28. data/app/views/reports/_list.erb +9 -0
  29. data/app/views/reports/edit.erb +2 -0
  30. data/app/views/reports/index.erb +3 -0
  31. data/app/views/reports/new.erb +2 -0
  32. data/app/views/reports/show.erb +23 -0
  33. data/app/views/tables/_form.erb +8 -0
  34. data/app/views/tables/_list.erb +9 -0
  35. data/app/views/tables/edit.erb +2 -0
  36. data/app/views/tables/index.erb +3 -0
  37. data/app/views/tables/new.erb +2 -0
  38. data/app/views/tables/show.erb +35 -0
  39. data/app/views/users/_form.erb +5 -0
  40. data/app/views/users/_items.erb +8 -0
  41. data/app/views/users/_list.erb +9 -0
  42. data/app/views/users/edit.erb +2 -0
  43. data/app/views/users/index.erb +3 -0
  44. data/app/views/users/new.erb +2 -0
  45. data/app/views/users/show.erb +4 -0
  46. data/config/application.rb +52 -0
  47. data/config/authorization_rules.rb +49 -0
  48. data/config/boot.rb +6 -0
  49. data/config/daily.example.yml +19 -0
  50. data/config/daily.yml +27 -0
  51. data/config/database.yml +25 -0
  52. data/config/environment.rb +5 -0
  53. data/config/environments/development.rb +32 -0
  54. data/config/environments/production.rb +63 -0
  55. data/config/environments/test.rb +39 -0
  56. data/config/initializers/backtrace_silencers.rb +7 -0
  57. data/config/initializers/devise.rb +212 -0
  58. data/config/initializers/inflections.rb +10 -0
  59. data/config/initializers/mime_types.rb +5 -0
  60. data/config/initializers/ruport.rb +5 -0
  61. data/config/initializers/secret_token.rb +7 -0
  62. data/config/initializers/session_store.rb +8 -0
  63. data/config/initializers/simple_form.rb +113 -0
  64. data/config/initializers/wrap_parameters.rb +14 -0
  65. data/config/locales/devise.en.yml +58 -0
  66. data/config/locales/en.yml +5 -0
  67. data/config/locales/simple_form.en.yml +24 -0
  68. data/config/routes.rb +29 -0
  69. data/db/development.sqlite3 +0 -0
  70. data/db/migrate/20111109081414_devise_create_users.rb +28 -0
  71. data/db/migrate/20111111165640_create_tables.rb +14 -0
  72. data/db/migrate/20111112022333_add_name_to_tables.rb +9 -0
  73. data/db/migrate/20111112170802_add_reports.rb +15 -0
  74. data/db/migrate/20111113000026_add_guid_to_tables.rb +9 -0
  75. data/db/migrate/20111113073326_add_times_to_reports.rb +11 -0
  76. data/db/migrate/20111113075747_add_times_to_tables.rb +9 -0
  77. data/db/migrate/20111114041729_create_delayed_jobs.rb +21 -0
  78. data/db/migrate/20111114053016_add_report_to_delayed_jobs.rb +11 -0
  79. data/db/migrate/20111115014959_add_admin_to_users.rb +9 -0
  80. data/db/migrate/20111127065357_add_column_names_to_table.rb +9 -0
  81. data/db/migrate/20111203020425_add_transform_to_table.rb +16 -0
  82. data/db/migrate/20111214020029_add_formatter_data_to_reports.rb +8 -0
  83. data/db/schema.rb +80 -0
  84. data/db/seeds.rb +7 -0
  85. data/db/test.sqlite3 +0 -0
  86. data/lib/daily.rb +9 -0
  87. data/lib/daily/daily_config.rb +51 -0
  88. data/lib/daily/engine.rb +4 -0
  89. data/lib/daily/has_data.rb +52 -0
  90. data/lib/daily/shared_behaviors.rb +65 -0
  91. data/lib/daily/version.rb +3 -0
  92. data/lib/tasks/user.rake +24 -0
  93. 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,5 @@
1
+ class FilesController < ApplicationController
2
+ def show
3
+ send_file Rails.root.join("files/#{params[:path]}")
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class MainController < ApplicationController
2
+ def home
3
+ redirect_to root_url unless current_user
4
+ end
5
+ 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("&amp;nbsp;", "&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>&nbsp;</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(" ", "&nbsp;")) 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(" ", "&nbsp;")) unless object.transform_data.blank?
83
+ out.html_safe
84
+ end
85
+ end
@@ -0,0 +1,13 @@
1
+ class GenerateReportJob < Struct.new(:report_id, :requeue)
2
+
3
+ def report
4
+ @report ||= Report.find_by_id(report_id)
5
+ end
6
+
7
+ def perform
8
+ return if report.nil?
9
+ report.generate!
10
+ report.queue_next! if requeue
11
+ end
12
+
13
+ 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