1 | import calendar, operator, os, socket |
---|
2 | from datetime import * |
---|
3 | from time import mktime, strftime, localtime |
---|
4 | from galaxy.web.base.controller import * |
---|
5 | from galaxy import model, util |
---|
6 | from galaxy.web.framework.helpers import time_ago, iff, grids |
---|
7 | from galaxy.model.orm import * |
---|
8 | import pkg_resources |
---|
9 | pkg_resources.require( "SQLAlchemy >= 0.4" ) |
---|
10 | import sqlalchemy as sa |
---|
11 | import logging |
---|
12 | log = logging.getLogger( __name__ ) |
---|
13 | |
---|
14 | class SpecifiedDateListGrid( grids.Grid ): |
---|
15 | class JobIdColumn( grids.IntegerColumn ): |
---|
16 | def get_value( self, trans, grid, job ): |
---|
17 | return job.id |
---|
18 | class StateColumn( grids.TextColumn ): |
---|
19 | def get_value( self, trans, grid, job ): |
---|
20 | return '<div class="count-box state-color-%s">%s</div>' % ( job.state, job.state ) |
---|
21 | def filter( self, trans, user, query, column_filter ): |
---|
22 | if column_filter == 'Unfinished': |
---|
23 | return query.filter( not_( or_( model.Job.table.c.state == model.Job.states.OK, |
---|
24 | model.Job.table.c.state == model.Job.states.ERROR, |
---|
25 | model.Job.table.c.state == model.Job.states.DELETED ) ) ) |
---|
26 | return query |
---|
27 | class ToolColumn( grids.TextColumn ): |
---|
28 | def get_value( self, trans, grid, job ): |
---|
29 | return job.tool_id |
---|
30 | class CreateTimeColumn( grids.DateTimeColumn ): |
---|
31 | def get_value( self, trans, grid, job ): |
---|
32 | return job.create_time |
---|
33 | class UserColumn( grids.GridColumn ): |
---|
34 | def get_value( self, trans, grid, job ): |
---|
35 | if job.user: |
---|
36 | return job.user.email |
---|
37 | return 'anonymous' |
---|
38 | class EmailColumn( grids.GridColumn ): |
---|
39 | def filter( self, trans, user, query, column_filter ): |
---|
40 | if column_filter == 'All': |
---|
41 | return query |
---|
42 | return query.filter( and_( model.Job.table.c.user_id == model.User.table.c.id, |
---|
43 | model.User.table.c.email == column_filter ) ) |
---|
44 | class SpecifiedDateColumn( grids.GridColumn ): |
---|
45 | def filter( self, trans, user, query, column_filter ): |
---|
46 | if column_filter == 'All': |
---|
47 | return query |
---|
48 | # We are either filtering on a date like YYYY-MM-DD or on a month like YYYY-MM, |
---|
49 | # so we need to figure out which type of date we have |
---|
50 | if column_filter.count( '-' ) == 2: |
---|
51 | # We are filtering on a date like YYYY-MM-DD |
---|
52 | year, month, day = map( int, column_filter.split( "-" ) ) |
---|
53 | start_date = date( year, month, day ) |
---|
54 | end_date = start_date + timedelta( days=1 ) |
---|
55 | return query.filter( and_( self.model_class.table.c.create_time >= start_date, |
---|
56 | self.model_class.table.c.create_time < end_date ) ) |
---|
57 | if column_filter.count( '-' ) == 1: |
---|
58 | # We are filtering on a month like YYYY-MM |
---|
59 | year, month = map( int, column_filter.split( "-" ) ) |
---|
60 | start_date = date( year, month, 1 ) |
---|
61 | end_date = start_date + timedelta( days=calendar.monthrange( year, month )[1] ) |
---|
62 | return query.filter( and_( self.model_class.table.c.create_time >= start_date, |
---|
63 | self.model_class.table.c.create_time < end_date ) ) |
---|
64 | |
---|
65 | # Grid definition |
---|
66 | use_async = False |
---|
67 | model_class = model.Job |
---|
68 | title = "Jobs" |
---|
69 | template='/webapps/reports/grid.mako' |
---|
70 | default_sort_key = "id" |
---|
71 | columns = [ |
---|
72 | JobIdColumn( "Id", |
---|
73 | key="id", |
---|
74 | link=( lambda item: dict( operation="job_info", id=item.id, webapp="reports" ) ), |
---|
75 | attach_popup=False, |
---|
76 | filterable="advanced" ), |
---|
77 | StateColumn( "State", |
---|
78 | key="state", |
---|
79 | attach_popup=False ), |
---|
80 | ToolColumn( "Tool Id", |
---|
81 | key="tool_id", |
---|
82 | link=( lambda item: dict( operation="tool_per_month", id=item.id, webapp="reports" ) ), |
---|
83 | attach_popup=False ), |
---|
84 | CreateTimeColumn( "Creation Time", |
---|
85 | key="create_time", |
---|
86 | attach_popup=False ), |
---|
87 | UserColumn( "User", |
---|
88 | key="email", |
---|
89 | model_class=model.User, |
---|
90 | link=( lambda item: dict( operation="user_per_month", id=item.id, webapp="reports" ) ), |
---|
91 | attach_popup=False ), |
---|
92 | # Columns that are valid for filtering but are not visible. |
---|
93 | SpecifiedDateColumn( "Specified Date", |
---|
94 | key="specified_date", |
---|
95 | visible=False ), |
---|
96 | EmailColumn( "Email", |
---|
97 | key="email", |
---|
98 | model_class=model.User, |
---|
99 | visible=False ), |
---|
100 | grids.StateColumn( "State", |
---|
101 | key="state", |
---|
102 | visible=False, |
---|
103 | filterable="advanced" ) |
---|
104 | ] |
---|
105 | columns.append( grids.MulticolFilterColumn( "Search", |
---|
106 | cols_to_filter=[ columns[1], columns[2] ], |
---|
107 | key="free-text-search", |
---|
108 | visible=False, |
---|
109 | filterable="standard" ) ) |
---|
110 | standard_filters = [] |
---|
111 | default_filter = { 'specified_date' : 'All' } |
---|
112 | num_rows_per_page = 50 |
---|
113 | preserve_state = False |
---|
114 | use_paging = True |
---|
115 | def build_initial_query( self, trans, **kwd ): |
---|
116 | return trans.sa_session.query( self.model_class ) \ |
---|
117 | .join( model.User ) \ |
---|
118 | .enable_eagerloads( False ) |
---|
119 | |
---|
120 | class Jobs( BaseController ): |
---|
121 | |
---|
122 | specified_date_list_grid = SpecifiedDateListGrid() |
---|
123 | |
---|
124 | @web.expose |
---|
125 | def specified_date_handler( self, trans, **kwd ): |
---|
126 | # We add params to the keyword dict in this method in order to rename the param |
---|
127 | # with an "f-" prefix, simulating filtering by clicking a search link. We have |
---|
128 | # to take this approach because the "-" character is illegal in HTTP requests. |
---|
129 | if 'f-specified_date' in kwd and 'specified_date' not in kwd: |
---|
130 | # The user clicked a State link in the Advanced Search box, so 'specified_date' |
---|
131 | # will have been eliminated. |
---|
132 | pass |
---|
133 | elif 'specified_date' not in kwd: |
---|
134 | kwd[ 'f-specified_date' ] = 'All' |
---|
135 | else: |
---|
136 | kwd[ 'f-specified_date' ] = kwd[ 'specified_date' ] |
---|
137 | if 'operation' in kwd: |
---|
138 | operation = kwd['operation'].lower() |
---|
139 | if operation == "job_info": |
---|
140 | return trans.response.send_redirect( web.url_for( controller='jobs', |
---|
141 | action='job_info', |
---|
142 | **kwd ) ) |
---|
143 | elif operation == "tool_for_month": |
---|
144 | kwd[ 'f-tool_id' ] = kwd[ 'tool_id' ] |
---|
145 | elif operation == "tool_per_month": |
---|
146 | # The received id is the job id, so we need to get the job's tool_id. |
---|
147 | job_id = kwd.get( 'id', None ) |
---|
148 | job = get_job( trans, job_id ) |
---|
149 | kwd[ 'tool_id' ] = job.tool_id |
---|
150 | return trans.response.send_redirect( web.url_for( controller='jobs', |
---|
151 | action='tool_per_month', |
---|
152 | **kwd ) ) |
---|
153 | elif operation == "user_for_month": |
---|
154 | kwd[ 'f-email' ] = util.restore_text( kwd[ 'email' ] ) |
---|
155 | elif operation == "user_per_month": |
---|
156 | # The received id is the job id, so we need to get the id of the user |
---|
157 | # that submitted the job. |
---|
158 | job_id = kwd.get( 'id', None ) |
---|
159 | job = get_job( trans, job_id ) |
---|
160 | if job.user: |
---|
161 | kwd[ 'email' ] = job.user.email |
---|
162 | else: |
---|
163 | kwd[ 'email' ] = None # For anonymous users |
---|
164 | return trans.response.send_redirect( web.url_for( controller='jobs', |
---|
165 | action='user_per_month', |
---|
166 | **kwd ) ) |
---|
167 | elif operation == "specified_date_in_error": |
---|
168 | kwd[ 'f-state' ] = 'error' |
---|
169 | elif operation == "unfinished": |
---|
170 | kwd[ 'f-state' ] = 'Unfinished' |
---|
171 | return self.specified_date_list_grid( trans, **kwd ) |
---|
172 | @web.expose |
---|
173 | def specified_month_all( self, trans, **kwd ): |
---|
174 | params = util.Params( kwd ) |
---|
175 | message = '' |
---|
176 | monitor_email = params.get( 'monitor_email', 'monitor@bx.psu.edu' ) |
---|
177 | # If specified_date is not received, we'll default to the current month |
---|
178 | specified_date = kwd.get( 'specified_date', datetime.utcnow().strftime( "%Y-%m-%d" ) ) |
---|
179 | specified_month = specified_date[ :7 ] |
---|
180 | year, month = map( int, specified_month.split( "-" ) ) |
---|
181 | start_date = date( year, month, 1 ) |
---|
182 | end_date = start_date + timedelta( days=calendar.monthrange( year, month )[1] ) |
---|
183 | month_label = start_date.strftime( "%B" ) |
---|
184 | year_label = start_date.strftime( "%Y" ) |
---|
185 | q = sa.select( ( sa.func.date( model.Job.table.c.create_time ).label( 'date' ), |
---|
186 | sa.func.sum( sa.case( [ ( model.User.table.c.email == monitor_email, 1 ) ], else_=0 ) ).label( 'monitor_jobs' ), |
---|
187 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
188 | whereclause = sa.and_( model.Job.table.c.create_time >= start_date, |
---|
189 | model.Job.table.c.create_time < end_date ), |
---|
190 | from_obj = [ sa.outerjoin( model.Job.table, model.User.table ) ], |
---|
191 | group_by = [ 'date' ], |
---|
192 | order_by = [ sa.desc( 'date' ) ] ) |
---|
193 | jobs = [] |
---|
194 | for row in q.execute(): |
---|
195 | jobs.append( ( row.date.strftime( "%A" ), |
---|
196 | row.date, |
---|
197 | row.total_jobs - row.monitor_jobs, |
---|
198 | row.monitor_jobs, |
---|
199 | row.total_jobs, |
---|
200 | row.date.strftime( "%d" ) ) ) |
---|
201 | return trans.fill_template( '/webapps/reports/jobs_specified_month_all.mako', |
---|
202 | month_label=month_label, |
---|
203 | year_label=year_label, |
---|
204 | month=month, |
---|
205 | jobs=jobs, |
---|
206 | message=message ) |
---|
207 | @web.expose |
---|
208 | def specified_month_in_error( self, trans, **kwd ): |
---|
209 | params = util.Params( kwd ) |
---|
210 | message = '' |
---|
211 | # If specified_date is not received, we'll default to the current month |
---|
212 | specified_date = kwd.get( 'specified_date', datetime.utcnow().strftime( "%Y-%m-%d" ) ) |
---|
213 | specified_month = specified_date[ :7 ] |
---|
214 | year, month = map( int, specified_month.split( "-" ) ) |
---|
215 | start_date = date( year, month, 1 ) |
---|
216 | end_date = start_date + timedelta( days=calendar.monthrange( year, month )[1] ) |
---|
217 | month_label = start_date.strftime( "%B" ) |
---|
218 | year_label = start_date.strftime( "%Y" ) |
---|
219 | q = sa.select( ( sa.func.date( model.Job.table.c.create_time ).label( 'date' ), |
---|
220 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
221 | whereclause = sa.and_( model.Job.table.c.state == 'error', |
---|
222 | model.Job.table.c.create_time >= start_date, |
---|
223 | model.Job.table.c.create_time < end_date ), |
---|
224 | from_obj = [ sa.outerjoin( model.Job.table, model.User.table ) ], |
---|
225 | group_by = [ 'date' ], |
---|
226 | order_by = [ sa.desc( 'date' ) ] ) |
---|
227 | jobs = [] |
---|
228 | for row in q.execute(): |
---|
229 | jobs.append( ( row.date.strftime( "%A" ), |
---|
230 | row.date, |
---|
231 | row.total_jobs, |
---|
232 | row.date.strftime( "%d" ) ) ) |
---|
233 | return trans.fill_template( '/webapps/reports/jobs_specified_month_in_error.mako', |
---|
234 | month_label=month_label, |
---|
235 | year_label=year_label, |
---|
236 | month=month, |
---|
237 | jobs=jobs, |
---|
238 | message=message ) |
---|
239 | @web.expose |
---|
240 | def per_month_all( self, trans, **kwd ): |
---|
241 | params = util.Params( kwd ) |
---|
242 | message = '' |
---|
243 | monitor_email = params.get( 'monitor_email', 'monitor@bx.psu.edu' ) |
---|
244 | q = sa.select( ( sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ).label( 'date' ), |
---|
245 | sa.func.sum( sa.case( [( model.User.table.c.email == monitor_email, 1 )], else_=0 ) ).label( 'monitor_jobs' ), |
---|
246 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
247 | from_obj = [ sa.outerjoin( model.Job.table, model.User.table ) ], |
---|
248 | group_by = [ sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ) ], |
---|
249 | order_by = [ sa.desc( 'date' ) ] ) |
---|
250 | jobs = [] |
---|
251 | for row in q.execute(): |
---|
252 | jobs.append( ( row.date.strftime( "%Y-%m" ), |
---|
253 | row.total_jobs - row.monitor_jobs, |
---|
254 | row.monitor_jobs, |
---|
255 | row.total_jobs, |
---|
256 | row.date.strftime( "%B" ), |
---|
257 | row.date.strftime( "%Y" ) ) ) |
---|
258 | return trans.fill_template( '/webapps/reports/jobs_per_month_all.mako', |
---|
259 | jobs=jobs, |
---|
260 | message=message ) |
---|
261 | @web.expose |
---|
262 | def per_month_in_error( self, trans, **kwd ): |
---|
263 | params = util.Params( kwd ) |
---|
264 | message = '' |
---|
265 | q = sa.select( ( sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ).label( 'date' ), |
---|
266 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
267 | whereclause = model.Job.table.c.state == 'error', |
---|
268 | from_obj = [ model.Job.table ], |
---|
269 | group_by = [ sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ) ], |
---|
270 | order_by = [ sa.desc( 'date' ) ] ) |
---|
271 | jobs = [] |
---|
272 | for row in q.execute(): |
---|
273 | jobs.append( ( row.date.strftime( "%Y-%m" ), |
---|
274 | row.total_jobs, |
---|
275 | row.date.strftime( "%B" ), |
---|
276 | row.date.strftime( "%Y" ) ) ) |
---|
277 | return trans.fill_template( '/webapps/reports/jobs_per_month_in_error.mako', |
---|
278 | jobs=jobs, |
---|
279 | message=message ) |
---|
280 | @web.expose |
---|
281 | def per_user( self, trans, **kwd ): |
---|
282 | params = util.Params( kwd ) |
---|
283 | message = '' |
---|
284 | jobs = [] |
---|
285 | q = sa.select( ( model.User.table.c.email.label( 'user_email' ), |
---|
286 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
287 | from_obj = [ sa.outerjoin( model.Job.table, model.User.table ) ], |
---|
288 | group_by = [ 'user_email' ], |
---|
289 | order_by = [ sa.desc( 'total_jobs' ), 'user_email' ] ) |
---|
290 | for row in q.execute(): |
---|
291 | jobs.append( ( row.user_email, |
---|
292 | row.total_jobs ) ) |
---|
293 | return trans.fill_template( '/webapps/reports/jobs_per_user.mako', jobs=jobs, message=message ) |
---|
294 | @web.expose |
---|
295 | def user_per_month( self, trans, **kwd ): |
---|
296 | params = util.Params( kwd ) |
---|
297 | message = '' |
---|
298 | email = util.restore_text( params.get( 'email', '' ) ) |
---|
299 | q = sa.select( ( sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ).label( 'date' ), |
---|
300 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
301 | whereclause = sa.and_( model.Job.table.c.session_id == model.GalaxySession.table.c.id, |
---|
302 | model.GalaxySession.table.c.user_id == model.User.table.c.id, |
---|
303 | model.User.table.c.email == email |
---|
304 | ), |
---|
305 | from_obj = [ sa.join( model.Job.table, model.User.table ) ], |
---|
306 | group_by = [ sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ) ], |
---|
307 | order_by = [ sa.desc( 'date' ) ] ) |
---|
308 | jobs = [] |
---|
309 | for row in q.execute(): |
---|
310 | jobs.append( ( row.date.strftime( "%Y-%m" ), |
---|
311 | row.total_jobs, |
---|
312 | row.date.strftime( "%B" ), |
---|
313 | row.date.strftime( "%Y" ) ) ) |
---|
314 | return trans.fill_template( '/webapps/reports/jobs_user_per_month.mako', |
---|
315 | email=util.sanitize_text( email ), |
---|
316 | jobs=jobs, message=message ) |
---|
317 | @web.expose |
---|
318 | def per_tool( self, trans, **kwd ): |
---|
319 | params = util.Params( kwd ) |
---|
320 | message = '' |
---|
321 | jobs = [] |
---|
322 | q = sa.select( ( model.Job.table.c.tool_id.label( 'tool_id' ), |
---|
323 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
324 | from_obj = [ model.Job.table ], |
---|
325 | group_by = [ 'tool_id' ], |
---|
326 | order_by = [ 'tool_id' ] ) |
---|
327 | for row in q.execute(): |
---|
328 | jobs.append( ( row.tool_id, |
---|
329 | row.total_jobs ) ) |
---|
330 | return trans.fill_template( '/webapps/reports/jobs_per_tool.mako', |
---|
331 | jobs=jobs, |
---|
332 | message=message ) |
---|
333 | @web.expose |
---|
334 | def tool_per_month( self, trans, **kwd ): |
---|
335 | params = util.Params( kwd ) |
---|
336 | message = '' |
---|
337 | tool_id = params.get( 'tool_id', 'Add a column1' ) |
---|
338 | specified_date = params.get( 'specified_date', datetime.utcnow().strftime( "%Y-%m-%d" ) ) |
---|
339 | q = sa.select( ( sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ).label( 'date' ), |
---|
340 | sa.func.count( model.Job.table.c.id ).label( 'total_jobs' ) ), |
---|
341 | whereclause = model.Job.table.c.tool_id == tool_id, |
---|
342 | from_obj = [ model.Job.table ], |
---|
343 | group_by = [ sa.func.date_trunc( 'month', sa.func.date( model.Job.table.c.create_time ) ) ], |
---|
344 | order_by = [ sa.desc( 'date' ) ] ) |
---|
345 | jobs = [] |
---|
346 | for row in q.execute(): |
---|
347 | jobs.append( ( row.date.strftime( "%Y-%m" ), |
---|
348 | row.total_jobs, |
---|
349 | row.date.strftime( "%B" ), |
---|
350 | row.date.strftime( "%Y" ) ) ) |
---|
351 | return trans.fill_template( '/webapps/reports/jobs_tool_per_month.mako', |
---|
352 | specified_date=specified_date, |
---|
353 | tool_id=tool_id, |
---|
354 | jobs=jobs, |
---|
355 | message=message ) |
---|
356 | @web.expose |
---|
357 | def job_info( self, trans, **kwd ): |
---|
358 | params = util.Params( kwd ) |
---|
359 | message = '' |
---|
360 | job = trans.sa_session.query( model.Job ) \ |
---|
361 | .get( trans.security.decode_id( kwd.get( 'id', '' ) ) ) |
---|
362 | return trans.fill_template( '/webapps/reports/job_info.mako', |
---|
363 | job=job, |
---|
364 | message=message ) |
---|
365 | @web.expose |
---|
366 | def per_domain( self, trans, **kwd ): |
---|
367 | # TODO: rewrite using alchemy |
---|
368 | params = util.Params( kwd ) |
---|
369 | message = '' |
---|
370 | engine = model.mapping.metadata.engine |
---|
371 | jobs = [] |
---|
372 | s = """ |
---|
373 | SELECT |
---|
374 | substr(bar.first_pass_domain, bar.dot_position, 4) AS domain, |
---|
375 | count(job_id) AS total_jobs |
---|
376 | FROM |
---|
377 | (SELECT |
---|
378 | user_email AS user_email, |
---|
379 | first_pass_domain, |
---|
380 | position('.' in first_pass_domain) AS dot_position, |
---|
381 | job_id AS job_id |
---|
382 | FROM |
---|
383 | (SELECT |
---|
384 | email AS user_email, |
---|
385 | substr(email, char_length(email)-3, char_length(email)) AS first_pass_domain, |
---|
386 | job.id AS job_id |
---|
387 | FROM |
---|
388 | job |
---|
389 | LEFT OUTER JOIN galaxy_session ON galaxy_session.id = job.session_id |
---|
390 | LEFT OUTER JOIN galaxy_user ON galaxy_session.user_id = galaxy_user.id |
---|
391 | WHERE |
---|
392 | job.session_id = galaxy_session.id |
---|
393 | AND |
---|
394 | galaxy_session.user_id = galaxy_user.id |
---|
395 | ) AS foo |
---|
396 | ) AS bar |
---|
397 | GROUP BY |
---|
398 | domain |
---|
399 | ORDER BY |
---|
400 | total_jobs DESC |
---|
401 | """ |
---|
402 | job_rows = engine.text( s ).execute().fetchall() |
---|
403 | for job in job_rows: |
---|
404 | jobs.append( ( job.domain, job.total_jobs ) ) |
---|
405 | return trans.fill_template( '/webapps/reports/jobs_per_domain.mako', jobs=jobs, message=message ) |
---|
406 | |
---|
407 | ## ---- Utility methods ------------------------------------------------------- |
---|
408 | |
---|
409 | def get_job( trans, id ): |
---|
410 | return trans.sa_session.query( trans.model.Job ).get( trans.security.decode_id( id ) ) |
---|