clark_kent 0.0.1

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/clark_kent/application.js +13 -0
  6. data/app/assets/javascripts/clark_kent/reports.js +10 -0
  7. data/app/assets/stylesheets/_reports.scss +67 -0
  8. data/app/assets/stylesheets/_reports_print.scss +44 -0
  9. data/app/assets/stylesheets/clark_kent/application.css +15 -0
  10. data/app/assets/stylesheets/clark_kent/reports.css +4 -0
  11. data/app/assets/stylesheets/scaffold.css +56 -0
  12. data/app/controllers/clark_kent/application_controller.rb +2 -0
  13. data/app/controllers/clark_kent/report_columns_controller.rb +47 -0
  14. data/app/controllers/clark_kent/report_emails_controller.rb +47 -0
  15. data/app/controllers/clark_kent/report_filters_controller.rb +51 -0
  16. data/app/controllers/clark_kent/reports_controller.rb +79 -0
  17. data/app/controllers/clark_kent/user_report_emails_controller.rb +53 -0
  18. data/app/helpers/clark_kent/application_helper.rb +70 -0
  19. data/app/mailers/clark_kent/report_mailer.rb +15 -0
  20. data/app/models/clark_kent/report.rb +272 -0
  21. data/app/models/clark_kent/report_column.rb +24 -0
  22. data/app/models/clark_kent/report_date_filter.rb +81 -0
  23. data/app/models/clark_kent/report_email.rb +105 -0
  24. data/app/models/clark_kent/report_filter.rb +26 -0
  25. data/app/models/clark_kent/report_filter_option.rb +18 -0
  26. data/app/models/clark_kent/report_number_filter.rb +20 -0
  27. data/app/models/clark_kent/report_object_filter.rb +27 -0
  28. data/app/models/clark_kent/report_result.rb +55 -0
  29. data/app/models/clark_kent/report_string_filter.rb +5 -0
  30. data/app/models/clark_kent/reportable.rb +148 -0
  31. data/app/models/clark_kent/sharing_scope.rb +34 -0
  32. data/app/models/clark_kent/sharing_scope_kind.rb +73 -0
  33. data/app/models/clark_kent/user_report_email.rb +19 -0
  34. data/app/validators/clark_kent/user_email_validator.rb +7 -0
  35. data/app/views/clark_kent/report_columns/_form.html.erb +50 -0
  36. data/app/views/clark_kent/report_columns/_index.html.erb +16 -0
  37. data/app/views/clark_kent/report_columns/_show.html.erb +20 -0
  38. data/app/views/clark_kent/report_columns/_show_wrapper.html.erb +3 -0
  39. data/app/views/clark_kent/report_emails/_edit.html.erb +12 -0
  40. data/app/views/clark_kent/report_emails/_form.html.erb +34 -0
  41. data/app/views/clark_kent/report_emails/_index.html.erb +16 -0
  42. data/app/views/clark_kent/report_emails/_show.html.erb +17 -0
  43. data/app/views/clark_kent/report_emails/_show_wrapper.html.erb +3 -0
  44. data/app/views/clark_kent/report_filters/_date_filter_edit.html.erb +9 -0
  45. data/app/views/clark_kent/report_filters/_date_filter_show.html.erb +1 -0
  46. data/app/views/clark_kent/report_filters/_form.html.erb +58 -0
  47. data/app/views/clark_kent/report_filters/_index.html.erb +16 -0
  48. data/app/views/clark_kent/report_filters/_number_filter_edit.html.erb +2 -0
  49. data/app/views/clark_kent/report_filters/_number_filter_show.html.erb +1 -0
  50. data/app/views/clark_kent/report_filters/_object_filter_edit.html.erb +1 -0
  51. data/app/views/clark_kent/report_filters/_object_filter_show.html.erb +1 -0
  52. data/app/views/clark_kent/report_filters/_show.html.erb +14 -0
  53. data/app/views/clark_kent/report_filters/_show_wrapper.html.erb +3 -0
  54. data/app/views/clark_kent/report_filters/_string_filter_edit.html.erb +0 -0
  55. data/app/views/clark_kent/report_filters/_string_filter_show.html.erb +1 -0
  56. data/app/views/clark_kent/report_mailer/report_run.html.erb +2 -0
  57. data/app/views/clark_kent/reports/_date_filter.html.erb +23 -0
  58. data/app/views/clark_kent/reports/_download_link.html.erb +14 -0
  59. data/app/views/clark_kent/reports/_edit.html.erb +47 -0
  60. data/app/views/clark_kent/reports/_form.html.erb +33 -0
  61. data/app/views/clark_kent/reports/_number_filter.html.erb +21 -0
  62. data/app/views/clark_kent/reports/_object_filter.html.erb +15 -0
  63. data/app/views/clark_kent/reports/_print_report.html.erb +30 -0
  64. data/app/views/clark_kent/reports/_show.html.erb +21 -0
  65. data/app/views/clark_kent/reports/_string_filter.html.erb +9 -0
  66. data/app/views/clark_kent/reports/edit.html.erb +21 -0
  67. data/app/views/clark_kent/reports/index.html.erb +22 -0
  68. data/app/views/clark_kent/reports/new.html.erb +9 -0
  69. data/app/views/clark_kent/reports/show.html.erb +43 -0
  70. data/app/views/clark_kent/user_report_emails/_form.html.erb +40 -0
  71. data/app/views/clark_kent/user_report_emails/_index.html.erb +16 -0
  72. data/app/views/clark_kent/user_report_emails/_show.html.erb +11 -0
  73. data/app/views/clark_kent/user_report_emails/_show_wrapper.html.erb +3 -0
  74. data/config/routes.rb +13 -0
  75. data/db/migrate/20150304233739_create_clark_kent_reports.rb +12 -0
  76. data/lib/clark_kent/engine.rb +5 -0
  77. data/lib/clark_kent/version.rb +3 -0
  78. data/lib/clark_kent.rb +20 -0
  79. data/lib/tasks/clark_kent_tasks.rake +4 -0
  80. data/test/clark_kent_test.rb +7 -0
  81. data/test/controllers/clark_kent/reports_controller_test.rb +51 -0
  82. data/test/dummy/README.rdoc +28 -0
  83. data/test/dummy/Rakefile +6 -0
  84. data/test/dummy/app/assets/javascripts/application.js +13 -0
  85. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  86. data/test/dummy/app/controllers/application_controller.rb +5 -0
  87. data/test/dummy/app/helpers/application_helper.rb +2 -0
  88. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  89. data/test/dummy/bin/bundle +3 -0
  90. data/test/dummy/bin/rails +4 -0
  91. data/test/dummy/bin/rake +4 -0
  92. data/test/dummy/config/application.rb +23 -0
  93. data/test/dummy/config/boot.rb +5 -0
  94. data/test/dummy/config/database.yml +25 -0
  95. data/test/dummy/config/environment.rb +5 -0
  96. data/test/dummy/config/environments/development.rb +37 -0
  97. data/test/dummy/config/environments/production.rb +78 -0
  98. data/test/dummy/config/environments/test.rb +39 -0
  99. data/test/dummy/config/initializers/assets.rb +8 -0
  100. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  101. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  102. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  103. data/test/dummy/config/initializers/inflections.rb +16 -0
  104. data/test/dummy/config/initializers/mime_types.rb +4 -0
  105. data/test/dummy/config/initializers/session_store.rb +3 -0
  106. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  107. data/test/dummy/config/locales/en.yml +23 -0
  108. data/test/dummy/config/routes.rb +4 -0
  109. data/test/dummy/config/secrets.yml +22 -0
  110. data/test/dummy/config.ru +4 -0
  111. data/test/dummy/db/development.sqlite3 +0 -0
  112. data/test/dummy/db/schema.rb +25 -0
  113. data/test/dummy/log/development.log +35 -0
  114. data/test/dummy/public/404.html +67 -0
  115. data/test/dummy/public/422.html +67 -0
  116. data/test/dummy/public/500.html +66 -0
  117. data/test/dummy/public/favicon.ico +0 -0
  118. data/test/dummy/tmp/cache/assets/development/sprockets/0a5b3da98f8307d16bc302a1f7206591 +0 -0
  119. data/test/dummy/tmp/cache/assets/development/sprockets/0a9995208f1340e4b34008cbd5b73c64 +0 -0
  120. data/test/dummy/tmp/cache/assets/development/sprockets/0ec37c0a58c1be93659732a3efc73581 +0 -0
  121. data/test/dummy/tmp/cache/assets/development/sprockets/0fd54fd98cd2fa0085b77e6743046927 +0 -0
  122. data/test/dummy/tmp/cache/assets/development/sprockets/5d3db72d44bc30497bd84a40d2002e12 +0 -0
  123. data/test/dummy/tmp/cache/assets/development/sprockets/60fb63be4cad769d9adc90c4c5501c67 +0 -0
  124. data/test/dummy/tmp/cache/assets/development/sprockets/8aa37926d964a9eb59cf9b940e4fe2f4 +0 -0
  125. data/test/dummy/tmp/cache/assets/development/sprockets/8dd3bd27ebbaecaf6c7ee8ed81be5bde +0 -0
  126. data/test/dummy/tmp/cache/assets/development/sprockets/92058832b745b88c29a75bf2aad7245d +0 -0
  127. data/test/dummy/tmp/cache/assets/development/sprockets/ba6c7581456ee0f828ace58e4856a9f4 +0 -0
  128. data/test/dummy/tmp/cache/assets/development/sprockets/ebe8eac74b8e6016fd44b19e6e708e61 +0 -0
  129. data/test/dummy/tmp/cache/assets/development/sprockets/f23dd414c3bac1b6833d2aa9e62fbedd +0 -0
  130. data/test/fixtures/clark_kent/reports.yml +13 -0
  131. data/test/helpers/clark_kent/reports_helper_test.rb +6 -0
  132. data/test/integration/navigation_test.rb +10 -0
  133. data/test/models/clark_kent/report_test.rb +9 -0
  134. data/test/test_helper.rb +17 -0
  135. metadata +260 -0
@@ -0,0 +1,272 @@
1
+ module ClarkKent
2
+ # load the builders
3
+ Dir.glob(Rails.root.join('app/models/reporting/*.rb')) { |file| load file }
4
+ class Report < ActiveRecord::Base
5
+
6
+ include Cloneable
7
+
8
+ SortDirections = {'A->Z' => 'asc', 'Z->A' => 'desc'}
9
+
10
+ attr_accessible :resource_type, :name, :sharing_scope_id, :sharing_scope_type
11
+ attr_accessor :summary_row_storage
12
+
13
+ belongs_to :sharing_scope, polymorphic: true
14
+ has_many :report_filters, as: :filterable, dependent: :destroy
15
+ has_many :report_columns, -> {order("clark_kent_report_columns.column_order").references(:report_columns)}, dependent: :destroy
16
+ has_many :report_emails, dependent: :destroy
17
+ has_many :report_email_filters, through: :report_emails, source: :report_filters
18
+
19
+ scope :for, ->(resource_type) { where(resource_type: resource_type) }
20
+ scope :shared, -> { where(sharing_scope_id: nil) }
21
+
22
+ validates :sharing_scope_id, presence: true, if: ->(r) { r.sharing_scope_type.present? }
23
+
24
+ def self.send_report_to_s3(report_id, params)
25
+ params = params
26
+ report_class = params['report_class'].constantize if params['report_class']
27
+ report_class ||= ::ClarkKent::Report
28
+ reportable = report_class.find(report_id)
29
+ report = ('ClarkKent::ReportEmail' == report_class.name) ? reportable.report : reportable
30
+ query = reportable.get_query(params)
31
+ row_count = reportable.get_query(params, true)
32
+ bucket = AWS::S3::Bucket.new(ClarkKent::ReportUploaderBucketName)
33
+ report_destination = bucket.objects[params['report_result_name']]
34
+ byte_count = 0
35
+ temp_buffer = report.headers.to_csv
36
+ offset = 0
37
+ summary_rows = []
38
+ some_left = true
39
+ report_destination.write(estimated_content_length: row_count * 1000) do |buffer, bytes|
40
+ while temp_buffer.length < bytes and some_left
41
+ this_batch = query.offset(offset).limit(100)
42
+ some_left = this_batch.each do |row|
43
+ temp_buffer << report.report_columns.map{ |column| row[column.column_name] }.to_csv
44
+ end.any?
45
+ summary_rows.push report.summary_row(this_batch) if report.summary_row? and some_left
46
+ offset += 100
47
+ end
48
+ if report.summary_row_storage.blank? and (!some_left)
49
+ if report.summary_row? and summary_rows.any?
50
+ summary_row_map = report.summary_row(summary_rows)
51
+ report.summary_row_storage = report.report_columns.map do |col|
52
+ summary_row_map.send col.column_name
53
+ end
54
+ temp_buffer << report.summary_row_storage.to_csv
55
+ end
56
+ end
57
+ if temp_buffer.present?
58
+ chunk = temp_buffer.slice!(0...bytes)
59
+ buffer << chunk
60
+ end
61
+ end
62
+ report_result_url = report_destination.url_for(
63
+ :read,
64
+ secure: true,
65
+ response_content_type: 'text/csv',
66
+ response_content_encoding: 'binary',
67
+ expires: 60*60*24*30 #good for 30 days
68
+ )
69
+ if 'ClarkKent::Report' == report_class.name
70
+ ForeignOffice.publish(channel: params['report_result_name'], object: {report_result_url: report_result_url.to_s} )
71
+ end
72
+ report_result_url.to_s
73
+ end
74
+
75
+ def get_query(params, count = false)
76
+ self.resource_class.report(params,self, count)
77
+ end
78
+
79
+ def resource_class
80
+ @resource_class ||= self.resource_type.constantize
81
+ end
82
+
83
+ def sort_column
84
+ @sort_column ||= self.report_columns.where("clark_kent_report_columns.report_sort is not NULL and clark_kent_report_columns.report_sort != ''").first
85
+ end
86
+
87
+ def sorter
88
+ if self.sort_column
89
+ sort_column_name = self.sort_column.column_name
90
+ sort_direction = Report::SortDirections[sort_column.report_sort]
91
+ Map.new(order_column: sort_column_name, order_direction: sort_direction)
92
+ end
93
+ end
94
+
95
+ def arel_includes
96
+ self.report_columns.map{|column|
97
+ column_info = self.column_options_for(column.column_name)
98
+ column_info.includes if column_info.respond_to? :includes
99
+ }.compact
100
+ end
101
+
102
+ def arel_joins
103
+ self.report_columns.map{|column|
104
+ column_info = self.column_options_for(column.column_name)
105
+ column_info.joins if column_info.respond_to? :joins
106
+ }.compact
107
+ end
108
+
109
+ def extra_scopes
110
+ self.report_columns.map{|column|
111
+ column_info = self.column_options_for(column.column_name)
112
+ column_info.extra_scopes if column_info.respond_to? :extra_scopes
113
+ }.flatten.compact
114
+ end
115
+
116
+ def extra_filters
117
+ self.report_columns.map{|column|
118
+ column_info = self.column_options_for(column.column_name)
119
+ column_info.where if column_info.respond_to? :where
120
+ }.flatten.compact
121
+ end
122
+
123
+ def groups
124
+ self.report_columns.map{|column|
125
+ column_info = self.column_options_for(column.column_name)
126
+ column_info.group if column_info.respond_to? :group
127
+ }.flatten.compact
128
+ end
129
+
130
+ def report_filter_params
131
+ Hash[*self.report_filters.map{|filter| filter.filter_match_params}.flatten].
132
+ merge(order: self.sorter)
133
+ end
134
+
135
+ def select_clauses
136
+ @selects = []
137
+ self.report_columns.each do |report_column|
138
+ column_option = self.column_options_for(report_column.column_name)
139
+ @selects.push column_option.custom_select if column_option.respond_to? :custom_select
140
+ end
141
+ self.report_filters.each do |report_filter|
142
+ column_option = self.column_options_for(report_filter.filter_name)
143
+ @selects.push column_option.custom_select if column_option.respond_to? :custom_select
144
+ end
145
+ @selects
146
+ end
147
+
148
+
149
+ def filter_options_for(filter_name)
150
+ self.resource_class::REPORT_FILTER_OPTIONS.detect{|filter| filter.param == filter_name}
151
+ end
152
+
153
+ def column_options_for(column_name)
154
+ if self.resource_class::REPORT_COLUMN_OPTIONS.has_key? column_name.to_sym
155
+ self.resource_class::REPORT_COLUMN_OPTIONS[column_name.to_sym]
156
+ else
157
+ column_name = column_name.to_s.split('_')[0..-2].join('_')
158
+ if self.resource_class::REPORT_COLUMN_OPTIONS.has_key? column_name.to_sym
159
+ self.resource_class::REPORT_COLUMN_OPTIONS[column_name.to_sym]
160
+ end
161
+ end
162
+ end
163
+
164
+ def filter_kind(filter_name)
165
+ self.filter_options_for(filter_name).kind
166
+ end
167
+
168
+ def date_filter_names
169
+ self.resource_class::REPORT_FILTER_OPTIONS.select{|filter| 'date_filter' == filter.kind}.map{|filter| filter.param}
170
+ end
171
+
172
+ def available_filters
173
+ self.available_email_filters.reject{|name| self.date_filter_names.include? name}
174
+ end
175
+
176
+ def available_filter_options
177
+ self.available_filters.map{|id| [self.filter_options_for(id).label,id]}
178
+ end
179
+
180
+ def available_email_filters
181
+ self.resource_class::REPORT_DEFINITION_OPTIONS.reject{|name| (self.report_filters.pluck(:filter_name)).include? name}
182
+ end
183
+
184
+ def collection_for(filter_name)
185
+ self.resource_class::REPORT_FILTER_OPTIONS.detect{|filter| filter.param == filter_name}.collection
186
+ end
187
+
188
+ def custom_filters
189
+ self.resource_class::REPORT_FILTER_OPTIONS.select{|filter| self.report_filters.pluck(:filter_name).exclude? filter.param}
190
+ end
191
+
192
+ def available_columns
193
+ self.resource_class::REPORT_COLUMN_OPTIONS.keys.reject{|column| self.report_columns.pluck(:column_name).include? column.to_s}
194
+ end
195
+
196
+ def sortable?(column)
197
+ !!(self.column_options_for(column.column_name).respond_to? :order_sql)
198
+ end
199
+
200
+ def sharing_scope_pretty
201
+ (self.sharing_scope.try :name ) || 'Everyone'
202
+ end
203
+
204
+ def resource_type_pretty
205
+ self.resource_class.prettify_name.pluralize
206
+ end
207
+
208
+ def get_filter_class(params)
209
+ filter_option = self.resource_class::REPORT_FILTER_OPTIONS.detect{|filter| filter.param == params[:filter_name]}
210
+ "ClarkKent::Report#{filter_option.kind.camelcase}".constantize
211
+ end
212
+
213
+ def summary_row_values(rows)
214
+ self.report_columns.each_with_index.map do |report_column,index|
215
+ report_column.calculate_summary(rows.map{|row| row[index]})
216
+ end
217
+ end
218
+
219
+ def summary_row(rows)
220
+ row_array = self.report_columns.map do |report_column|
221
+ [report_column.column_name,report_column.calculate_summary(rows.map{|row| row.send(report_column.column_name)})]
222
+ end
223
+ Map.new(row_array.to_h)
224
+ end
225
+
226
+ def summary_row?
227
+ @summary_row_presence ||= self.report_columns.to_a.any?{|c| c.summary_method.present? }
228
+ end
229
+
230
+ def headers
231
+ the_headers = self.report_columns.sorted.pluck(:column_name)
232
+
233
+ unless name =~ /net\s?promoter/i
234
+ the_headers.map(&:humanize)
235
+ else
236
+ the_headers
237
+ end
238
+ end
239
+
240
+ def deep_clone
241
+ Report.transaction do
242
+ new_report = dup.reset_timestamps
243
+ new_report.name << " CLONED: #{Date.today.to_s(:db)}"
244
+ new_report.save!
245
+
246
+ report_filters.each do |report_filter|
247
+ new_report.report_filters << report_filter.dup.reset_timestamps
248
+ end
249
+
250
+ report_columns.each do |report_column|
251
+ new_report.report_columns << report_column.dup.reset_timestamps
252
+ end
253
+
254
+ report_emails.each do |report_email|
255
+ new_report_email = report_email.dup.reset_timestamps
256
+ new_report.report_emails << new_report_email
257
+
258
+ report_email.report_filters.each do |report_filter|
259
+ new_report_email.report_filters << report_filter.dup.reset_timestamps
260
+ end
261
+
262
+ report_email.user_report_emails.each do |user_report_email|
263
+ new_report_email.user_report_emails << user_report_email.dup.reset_timestamps
264
+ end
265
+ end
266
+
267
+ new_report
268
+ end
269
+ end
270
+
271
+ end
272
+ end
@@ -0,0 +1,24 @@
1
+ module ClarkKent
2
+ class ReportColumn < ActiveRecord::Base
3
+ include Cloneable
4
+
5
+ SummaryMethods = ['total','average']
6
+ attr_accessible :report_id, :column_name, :column_order, :report_sort, :summary_method
7
+ belongs_to :report
8
+
9
+ scope :sorted, -> { order("clark_kent_report_columns.column_order") }
10
+
11
+ def report_sort_pretty
12
+ {'ascending' => 'A->Z','descending' => 'Z->A'}[self.report_sort]
13
+ end
14
+
15
+ def calculate_summary(values)
16
+ return nil unless self.summary_method.present?
17
+ values.send self.summary_method
18
+ end
19
+
20
+ def summarizable?
21
+ report.column_options_for(self.column_name).respond_to? :summarizable
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,81 @@
1
+ module ClarkKent
2
+ class ReportDateFilter < ReportFilter
3
+ WeekDayOptions = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
4
+ MonthDayOptions = ['beginning of month']
5
+ DayOptions = WeekDayOptions + MonthDayOptions
6
+ WeekPeriodOptions = {
7
+ 'previous week' => 'last_week',
8
+ 'same week' => 'this_week',
9
+ 'following week' => 'next_week'
10
+ }
11
+ MonthPeriodOptions = {
12
+ 'previous month' => 'last_month',
13
+ 'same month' => 'this_month',
14
+ 'following month' => 'next_month'
15
+ }
16
+
17
+ PeriodOptions = WeekPeriodOptions.merge MonthPeriodOptions
18
+
19
+ Durations = ['day','week','two weeks','month']
20
+
21
+ before_save :handle_filter_value
22
+
23
+ attr_accessible :kind_of_day, :offset, :duration
24
+
25
+ def filter_match_params
26
+ [[self.begin_param_name,self.begin_date],[self.end_param_name,self.end_date]]
27
+ end
28
+
29
+ def begin_param_name
30
+ "#{self.filter_name}_from"
31
+ end
32
+
33
+ def end_param_name
34
+ "#{self.filter_name}_until"
35
+ end
36
+
37
+ def begin_date
38
+ @begin_date = Date.ih_today
39
+ direction, period = self.period_offset
40
+ @begin_date = @begin_date.send(direction, 1.send(period)) if direction
41
+ @begin_date = @begin_date.find_day(self.day_offset) if self.day_offset
42
+ @begin_date
43
+ end
44
+
45
+ def day_offset
46
+ return false if 'today' == self.kind_of_day
47
+ self.kind_of_day.gsub(/ /,'_')
48
+ end
49
+
50
+ def period_offset
51
+ direction, period = self.offset.split('_')
52
+ direction = {'last' => '-', 'next' => '+', 'this' => false, '' => false}[direction.to_s]
53
+ return [direction, period]
54
+ end
55
+
56
+ def end_date
57
+ return self.begin_date if 'day' == self.duration
58
+ return self.begin_date + 6.days if 'week' == self.duration
59
+ return self.begin_date + 13.days if 'two weeks' == self.duration
60
+ if Date::DAYNAMES.include? self.kind_of_day.capitalize
61
+ return self.begin_date + 1.month
62
+ else
63
+ return self.begin_date.end_of_month
64
+ end
65
+ end
66
+
67
+ def handle_filter_value
68
+ if self.filter_value_1.present? and self.filter_value_2.present?
69
+ self.filter_value = [filter_value_1, self.filter_value_2].join(' ')
70
+ end
71
+ end
72
+
73
+ def offset_pretty
74
+ self.class::PeriodOptions.rassoc(self.offset).first
75
+ end
76
+
77
+ def date_display
78
+ "starts on #{self.kind_of_day} #{self.offset_pretty} for 1 #{self.duration}"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,105 @@
1
+ module ClarkKent
2
+ class ReportEmail < ActiveRecord::Base
3
+ include Cloneable
4
+
5
+ SEND_TIMES = {
6
+ 'Mondays' => 'Monday',
7
+ 'Tuesdays' => 'Tuesday',
8
+ 'Wednesdays' => 'Wednesday',
9
+ 'Thursdays' => 'Thursday',
10
+ 'Fridays' => 'Friday',
11
+ 'Saturdays' => 'Saturday',
12
+ 'Sundays' => 'Sunday',
13
+ '1st of the month' => 'beginning_of_month',
14
+ 'End of the month' => 'end_of_month'
15
+ }
16
+ belongs_to :report
17
+ has_many :report_filters, as: :filterable, dependent: :destroy
18
+ has_many :report_date_filters, as: :filterable, dependent: :destroy
19
+ has_many :user_report_emails, dependent: :destroy
20
+ has_many :users, through: :user_report_emails
21
+
22
+ attr_accessible :report_id, :when_to_send, :name
23
+
24
+ def self.send_emails_for_today
25
+ today = Date.ih_today
26
+ todays_filters = []
27
+ ['beginning_of_month','end_of_month'].each do |month_bookend|
28
+ todays_filters.push month_bookend if today.send(month_bookend) == today
29
+ end
30
+ todays_filters.push Date::DAYNAMES[today.wday]
31
+ self.where(when_to_send: todays_filters).each do |report_email|
32
+ report_email.send_emails
33
+ end
34
+ end
35
+
36
+ def send_emails
37
+ self.user_report_emails.each do |user_report_email|
38
+ ConeyIsland.submit(ClarkKent::ReportEmail, :send_email, instance_id: self.id, args: [user_report_email.user_id], timeout: 300, work_queue: 'boardwalk')
39
+ end
40
+ end
41
+
42
+ def send_email(user_id)
43
+ user = ClarkKent.user_class.find(user_id)
44
+ params = {'report_result_name' => "report-#{self.id}-user-#{user_id}-#{Time.now.to_formatted_s(:number)}", 'report_class' => 'ClarkKent::ReportEmail'}
45
+ SharingScopeKind.custom.each do |sharing_scope_kind|
46
+ unless report.report_filters.map(&:filter_name).include? sharing_scope_kind.basic_association_id_collection_name.to_s
47
+ associations = sharing_scope_kind.associated_containers_for(user)
48
+ params[sharing_scope_kind.basic_association_id_collection_name] = associations.map(&:id)
49
+ end
50
+ end
51
+ report_download_url = ClarkKent::Report.send_report_to_s3(self.id, params)
52
+ ClarkKent::ReportMailer.report_run(self.report_id, user_id, report_download_url).deliver
53
+ end
54
+
55
+ def report_filter_params
56
+ Hash[*self.report_filters.map{|filter| filter.filter_match_params}.flatten].
57
+ merge(order: self.report.sorter).merge(self.report.report_filter_params)
58
+ end
59
+
60
+ def filter_kind(filter_name)
61
+ self.report.filter_kind(filter_name)
62
+ end
63
+
64
+ def resource_class
65
+ self.report.resource_class
66
+ end
67
+
68
+ def filter_options_for(filter_name)
69
+ self.report.filter_options_for(filter_name)
70
+ end
71
+
72
+ def collection_for(filter_name)
73
+ self.report.collection_for(filter_name)
74
+ end
75
+
76
+ def get_filter_class(params)
77
+ self.report.get_filter_class(params)
78
+ end
79
+
80
+ def available_email_filters
81
+ self.resource_class::REPORT_DEFINITION_OPTIONS.reject{|name, label| (self.report_filters.pluck(:filter_name) + self.report.report_filters.pluck(:filter_name)).include? name}
82
+ end
83
+
84
+ def available_filters
85
+ self.available_email_filters
86
+ end
87
+
88
+ def available_filter_options
89
+ self.available_filters.map{|id| [self.filter_options_for(id).label,id]}
90
+ end
91
+
92
+ def get_query(params, count = false)
93
+ self.report.resource_class.report(params,self, count)
94
+ end
95
+
96
+ def period_pretty
97
+ self.report_date_filters.map{|filter| [filter.filter_name,filter.date_display].join(' ')}.join('<br>').html_safe
98
+ end
99
+
100
+ def emails
101
+ self.users.map(&:email)
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,26 @@
1
+ module ClarkKent
2
+ class ReportFilter < ActiveRecord::Base
3
+ include Cloneable
4
+
5
+ attr_accessor :filter_value_1, :filter_value_2
6
+ attr_accessible :filter_value_1, :filter_value_2, :report_id, :filter_name, :filter_value, :filterable_id, :filterable_type, :type
7
+ belongs_to :filterable, polymorphic: true
8
+
9
+ def filter_match_params
10
+ [self.filter_match_param,self.filter_match_value]
11
+ end
12
+
13
+ def filter_match_param
14
+ self.filter_name
15
+ end
16
+
17
+ def filter_match_value
18
+ self.filter_value
19
+ end
20
+
21
+ def display_name
22
+ self.filter_name
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module ClarkKent
2
+ class ReportFilterOption
3
+ include Cloneable
4
+
5
+ attr_accessor :param, :label, :collection, :kind, :select
6
+ def initialize(params)
7
+ self.param = params[:param] if params[:param].present?
8
+ self.label = params[:label] if params[:label].present?
9
+ self.collection = params[:collection] if params[:collection].present?
10
+ self.kind = params[:kind] if params[:kind].present?
11
+ self.select = params[:select] if params[:select].present?
12
+ end
13
+
14
+ def label
15
+ @label || @param
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module ClarkKent
2
+ class ReportNumberFilter < ReportFilter
3
+ include Cloneable
4
+
5
+ attr_accessible :max_value, :min_value
6
+
7
+ def filter_match_params
8
+ [[self.min_param_name,self.min_value],[self.max_param_name,self.max_value]]
9
+ end
10
+
11
+ def min_param_name
12
+ "#{self.filter_name}_min"
13
+ end
14
+
15
+ def max_param_name
16
+ "#{self.filter_name}_max"
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ module ClarkKent
2
+ class ReportObjectFilter < ReportFilter
3
+ include Cloneable
4
+
5
+ def get_display_value
6
+ if self.filter_value.to_i > 0
7
+ self.filter_class.find(self.filter_value).name
8
+ else
9
+ self.filter_value
10
+ end
11
+ end
12
+
13
+ def filter_class
14
+ if self.filter_name =~ /_id/
15
+ self.filter_name.split('_')[0..-2].join('_').camelcase.constantize
16
+ end
17
+ end
18
+
19
+ def display_name
20
+ if self.filter_class.present?
21
+ self.filter_class.name.underscore.humanize
22
+ else
23
+ self.filter_name
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ module ClarkKent
2
+ class ReportResult
3
+ include Enumerable
4
+
5
+ def initialize(arel_query, params)
6
+ @arel_query = arel_query
7
+ @params = params
8
+ end
9
+
10
+ def paginated_query
11
+ if @params.has_key? :page and @params.has_key? :per
12
+ page = @params[:page] || 1
13
+ @arel_query.offset((page.to_i - 1) * @params[:per]).limit(@params[:per])
14
+ else
15
+ @arel_query
16
+ end
17
+ end
18
+
19
+ def current_page
20
+ @params[:page]
21
+ end
22
+
23
+ def per_page
24
+ @params[:per]
25
+ end
26
+
27
+ def query
28
+ @arel_query
29
+ end
30
+
31
+ def total_count
32
+ unless defined?(@total_count)
33
+ results = results_for(@arel_query.to_sql)
34
+ @total_count = results.num_tuples
35
+ end
36
+
37
+ @total_count
38
+ end
39
+
40
+ def rows
41
+ results_for(paginated_query.to_sql)
42
+ end
43
+
44
+ def each(&block)
45
+ rows.each(&block)
46
+ end
47
+
48
+ private
49
+
50
+ def results_for(sql)
51
+ Report.connection.raw_connection.exec(sql)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ module ClarkKent
2
+ class ReportStringFilter < ReportFilter
3
+ include Cloneable
4
+ end
5
+ end