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