clark_kent 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/clark_kent/application.js +13 -0
- data/app/assets/javascripts/clark_kent/reports.js +10 -0
- data/app/assets/stylesheets/_reports.scss +67 -0
- data/app/assets/stylesheets/_reports_print.scss +44 -0
- data/app/assets/stylesheets/clark_kent/application.css +15 -0
- data/app/assets/stylesheets/clark_kent/reports.css +4 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/clark_kent/application_controller.rb +2 -0
- data/app/controllers/clark_kent/report_columns_controller.rb +47 -0
- data/app/controllers/clark_kent/report_emails_controller.rb +47 -0
- data/app/controllers/clark_kent/report_filters_controller.rb +51 -0
- data/app/controllers/clark_kent/reports_controller.rb +79 -0
- data/app/controllers/clark_kent/user_report_emails_controller.rb +53 -0
- data/app/helpers/clark_kent/application_helper.rb +70 -0
- data/app/mailers/clark_kent/report_mailer.rb +15 -0
- data/app/models/clark_kent/report.rb +272 -0
- data/app/models/clark_kent/report_column.rb +24 -0
- data/app/models/clark_kent/report_date_filter.rb +81 -0
- data/app/models/clark_kent/report_email.rb +105 -0
- data/app/models/clark_kent/report_filter.rb +26 -0
- data/app/models/clark_kent/report_filter_option.rb +18 -0
- data/app/models/clark_kent/report_number_filter.rb +20 -0
- data/app/models/clark_kent/report_object_filter.rb +27 -0
- data/app/models/clark_kent/report_result.rb +55 -0
- data/app/models/clark_kent/report_string_filter.rb +5 -0
- data/app/models/clark_kent/reportable.rb +148 -0
- data/app/models/clark_kent/sharing_scope.rb +34 -0
- data/app/models/clark_kent/sharing_scope_kind.rb +73 -0
- data/app/models/clark_kent/user_report_email.rb +19 -0
- data/app/validators/clark_kent/user_email_validator.rb +7 -0
- data/app/views/clark_kent/report_columns/_form.html.erb +50 -0
- data/app/views/clark_kent/report_columns/_index.html.erb +16 -0
- data/app/views/clark_kent/report_columns/_show.html.erb +20 -0
- data/app/views/clark_kent/report_columns/_show_wrapper.html.erb +3 -0
- data/app/views/clark_kent/report_emails/_edit.html.erb +12 -0
- data/app/views/clark_kent/report_emails/_form.html.erb +34 -0
- data/app/views/clark_kent/report_emails/_index.html.erb +16 -0
- data/app/views/clark_kent/report_emails/_show.html.erb +17 -0
- data/app/views/clark_kent/report_emails/_show_wrapper.html.erb +3 -0
- data/app/views/clark_kent/report_filters/_date_filter_edit.html.erb +9 -0
- data/app/views/clark_kent/report_filters/_date_filter_show.html.erb +1 -0
- data/app/views/clark_kent/report_filters/_form.html.erb +58 -0
- data/app/views/clark_kent/report_filters/_index.html.erb +16 -0
- data/app/views/clark_kent/report_filters/_number_filter_edit.html.erb +2 -0
- data/app/views/clark_kent/report_filters/_number_filter_show.html.erb +1 -0
- data/app/views/clark_kent/report_filters/_object_filter_edit.html.erb +1 -0
- data/app/views/clark_kent/report_filters/_object_filter_show.html.erb +1 -0
- data/app/views/clark_kent/report_filters/_show.html.erb +14 -0
- data/app/views/clark_kent/report_filters/_show_wrapper.html.erb +3 -0
- data/app/views/clark_kent/report_filters/_string_filter_edit.html.erb +0 -0
- data/app/views/clark_kent/report_filters/_string_filter_show.html.erb +1 -0
- data/app/views/clark_kent/report_mailer/report_run.html.erb +2 -0
- data/app/views/clark_kent/reports/_date_filter.html.erb +23 -0
- data/app/views/clark_kent/reports/_download_link.html.erb +14 -0
- data/app/views/clark_kent/reports/_edit.html.erb +47 -0
- data/app/views/clark_kent/reports/_form.html.erb +33 -0
- data/app/views/clark_kent/reports/_number_filter.html.erb +21 -0
- data/app/views/clark_kent/reports/_object_filter.html.erb +15 -0
- data/app/views/clark_kent/reports/_print_report.html.erb +30 -0
- data/app/views/clark_kent/reports/_show.html.erb +21 -0
- data/app/views/clark_kent/reports/_string_filter.html.erb +9 -0
- data/app/views/clark_kent/reports/edit.html.erb +21 -0
- data/app/views/clark_kent/reports/index.html.erb +22 -0
- data/app/views/clark_kent/reports/new.html.erb +9 -0
- data/app/views/clark_kent/reports/show.html.erb +43 -0
- data/app/views/clark_kent/user_report_emails/_form.html.erb +40 -0
- data/app/views/clark_kent/user_report_emails/_index.html.erb +16 -0
- data/app/views/clark_kent/user_report_emails/_show.html.erb +11 -0
- data/app/views/clark_kent/user_report_emails/_show_wrapper.html.erb +3 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20150304233739_create_clark_kent_reports.rb +12 -0
- data/lib/clark_kent/engine.rb +5 -0
- data/lib/clark_kent/version.rb +3 -0
- data/lib/clark_kent.rb +20 -0
- data/lib/tasks/clark_kent_tasks.rake +4 -0
- data/test/clark_kent_test.rb +7 -0
- data/test/controllers/clark_kent/reports_controller_test.rb +51 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +78 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/assets.rb +8 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +25 -0
- data/test/dummy/log/development.log +35 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0a5b3da98f8307d16bc302a1f7206591 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0a9995208f1340e4b34008cbd5b73c64 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0ec37c0a58c1be93659732a3efc73581 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0fd54fd98cd2fa0085b77e6743046927 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/5d3db72d44bc30497bd84a40d2002e12 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/60fb63be4cad769d9adc90c4c5501c67 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/8aa37926d964a9eb59cf9b940e4fe2f4 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/8dd3bd27ebbaecaf6c7ee8ed81be5bde +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/92058832b745b88c29a75bf2aad7245d +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ba6c7581456ee0f828ace58e4856a9f4 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ebe8eac74b8e6016fd44b19e6e708e61 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f23dd414c3bac1b6833d2aa9e62fbedd +0 -0
- data/test/fixtures/clark_kent/reports.yml +13 -0
- data/test/helpers/clark_kent/reports_helper_test.rb +6 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/models/clark_kent/report_test.rb +9 -0
- data/test/test_helper.rb +17 -0
- 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
|