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