diff --git a/lib/galaxy/webapps/reports/controllers/history.py b/lib/galaxy/webapps/reports/controllers/history.py
new file mode 100644
index 00000000000..7a75c6a2ba8
--- /dev/null
+++ b/lib/galaxy/webapps/reports/controllers/history.py
@@ -0,0 +1,192 @@
+import galaxy.model
+import collections
+import logging
+import sqlalchemy as sa
+
+from galaxy import util
+from galaxy.web.base.controller import BaseUIController, web
+
+from markupsafe import escape
+from sqlalchemy import and_
+
+log = logging.getLogger( __name__ )
+
+
+def int_to_octet(size):
+ try:
+ size = float(size)
+ except ValueError:
+ return "???"
+ except TypeError:
+ if size is None:
+ return "0 o"
+ return "???"
+ units = ("o", "Ko", "Mo", "Go", "To")
+ no_unit = 0
+ while (size >= 1000):
+ size /= 1000.
+ no_unit += 1
+ try:
+ return "%.2f %s" % (size, units[no_unit])
+ except IndexError:
+ return "%.0f %s" % (size * ((no_unit - len(units) + 1) * 1000.), units[-1] )
+
+
+class History( BaseUIController ):
+ """
+ Class defining functions used by reports to make requests to get
+ informations and fill templates before being displayed.
+ The name of function must be the same as as the field "action" of
+ the "href" dict, in .mako templates (templates/webapps/reports).
+ """
+ @web.expose
+ def history_and_dataset_per_user( self, trans, **kwd ):
+ """
+ fill history_and_dataset_per_user.mako template with:
+ - user email
+ - the number of history and their size
+ - the number of dataset
+ """
+ message = escape( util.restore_text( kwd.get( 'message', '' ) ) )
+ user_cutoff = int( kwd.get( 'user_cutoff', 60 ) )
+
+ # sort by history space, or by user mail or by number of history/dataset
+ sort_by = kwd.get( 'sorting', 'User' )
+ sorting = 0 if sort_by == 'User' else 1 if sort_by == "HSort" else 2 if sort_by == "DSort" else 3
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+
+ # select count (h.id) as history, u.email as email
+ # from history h, galaxy_user u
+ # where h.user_id = u.id and h.deleted='f'
+ # group by email order by email desc
+ histories = sa.select(
+ (sa.func.count( galaxy.model.History.table.c.id ).label( 'history' ),
+ galaxy.model.User.table.c.email.label( 'email' )),
+ from_obj=[sa.outerjoin(galaxy.model.History.table, galaxy.model.User.table)],
+ whereclause=and_(galaxy.model.History.table.c.user_id == galaxy.model.User.table.c.id,
+ galaxy.model.History.table.c.deleted == 'f'),
+ group_by=['email'],
+ order_by=[ sa.desc( 'email' ), 'history' ] )
+
+ # select u.email, count(d.id)
+ # from galaxy_user u, dataset d, history_dataset_association hd,history h
+ # where d.id=hd.dataset_id and h.id=hd.history_id and u.id = h.user_id and h.deleted='f'
+ # group by u.email;
+ datasets = sa.select(
+ (sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'dataset' ),
+ sa.func.sum( galaxy.model.Dataset.table.c.total_size ).label( 'size' ),
+ galaxy.model.User.table.c.email.label( 'email' ) ),
+ from_obj=[ galaxy.model.User.table,
+ galaxy.model.Dataset.table,
+ galaxy.model.HistoryDatasetAssociation.table,
+ galaxy.model.History.table],
+ whereclause=and_(galaxy.model.Dataset.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.dataset_id,
+ galaxy.model.History.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.history_id,
+ galaxy.model.History.table.c.user_id == galaxy.model.User.table.c.id,
+ galaxy.model.History.table.c.deleted == 'f'),
+ group_by=[ 'email' ] )
+
+ # execute requests, replace None fields by "Unknown"
+ # transform lists to dict with email as key and
+ # number of (history/dataset)/size of history as value
+ histories = dict( [ ( _.email if _.email is not None else "Unknown", int( _.history ) )
+ for _ in histories.execute() ] )
+ datasets = dict( [ ( _.email if _.email is not None else "Unknown", (int( _.dataset ), int( _.size )) )
+ for _ in datasets.execute() ] )
+
+ sorting_functions = [
+ lambda first, second: descending if first[0].lower() > second[0].lower() else -descending,
+ lambda first, second: descending if histories.get(first, 0) < histories.get(second, 0) else -descending,
+ lambda first, second: descending if datasets.get(first, [0])[0] < datasets.get(second, [0])[0] else -descending,
+ lambda first, second: descending if datasets.get(first, [0, 0])[1] < datasets.get(second, [0, 0])[1] else -descending
+ ]
+
+ # fetch all users
+ users = list(set(histories.keys()) | set(datasets.keys()))
+
+ # sort users depending on sort function, defined by user choices
+ users.sort(sorting_functions[sorting])
+ if user_cutoff > 0:
+ users = users[:user_cutoff]
+
+ # to keep ordered
+ data = collections.OrderedDict()
+ for user in users:
+ dataset = datasets.get(user, [0, 0])
+ history = histories.get(user, 0)
+ data[user] = ("%d (%s)" % ( history, int_to_octet( dataset[1] ) ), dataset[0] )
+
+ return trans.fill_template( '/webapps/reports/history_and_dataset_per_user.mako',
+ data=data,
+ user_cutoff=user_cutoff,
+ sorting=sorting,
+ descending=descending,
+ message=message )
+
+ @web.expose
+ def history_and_dataset_type( self, trans, **kwd ):
+ """
+ fill history_and_dataset_type.mako template with:
+ - the name of history
+ - the number of dataset foreach type
+ """
+ message = escape( util.restore_text( kwd.get( 'message', '' ) ) )
+ user_cutoff = int( kwd.get( 'user_cutoff', 60 ) )
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ user_selection = kwd.get( 'user_selection', None)
+
+ # select d.state, h.name
+ # from dataset d, history h , history_dataset_association hda
+ # where hda.history_id=h.id and hda.dataset_id=d.id order by h.state;
+ from_obj = [ galaxy.model.Dataset.table, galaxy.model.History.table, galaxy.model.HistoryDatasetAssociation.table]
+ if user_selection is not None:
+ from_obj.append( galaxy.model.User.table )
+ whereclause = and_( galaxy.model.Dataset.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.dataset_id,
+ galaxy.model.History.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.history_id,
+ galaxy.model.User.table.c.id == galaxy.model.History.table.c.user_id,
+ galaxy.model.User.table.c.email == user_selection )
+ else:
+ whereclause = and_(galaxy.model.Dataset.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.dataset_id,
+ galaxy.model.History.table.c.id == galaxy.model.HistoryDatasetAssociation.table.c.history_id)
+ histories = sa.select( ( galaxy.model.Dataset.table.c.state.label( 'state' ),
+ galaxy.model.History.table.c.name.label( 'name' ) ),
+ from_obj=from_obj,
+ whereclause=whereclause,
+ order_by=[ 'name' ] )
+
+ # execute requests, replace None fields by "Unknown"
+ data = [ ( _.name if _.name is not None else "NoNamedHistory", _.state )
+ for _ in histories.execute() ]
+
+ # sort by names descending or ascending
+ data.sort(lambda first, second: descending if first[0].lower() > second[0].lower() else -descending)
+
+ # fetch names in the first list and status in the second
+ if data:
+ names, status = zip( *tuple(data) )
+ else:
+ names, status = [], []
+
+ possible_status = {"ok": 0, "upload": 1, "paused": 2, "queued": 3, "error": 4, "discarded": 5}
+ number_of_possible_status = len( possible_status ) + 1 # + 1 to handle unknown status!
+
+ # to keep ordered
+ datas = collections.OrderedDict()
+ for no, name in enumerate(names):
+ if name not in datas:
+ if user_cutoff > 0:
+ if len( datas ) == user_cutoff:
+ break
+ # creation of a list containing the number of each status
+ datas[name] = ['-'] * number_of_possible_status
+ # to not execute it several times, we put it in a variable...
+ no_status = possible_status.get(status[no], 6)
+ if datas[name][no_status] == '-':
+ datas[name][no_status] = 0
+ datas[name][no_status] += 1
+
+ return trans.fill_template( '/webapps/reports/history_and_dataset_type.mako',
+ data=datas,
+ user_cutoff=user_cutoff,
+ descending=descending,
+ message=message )
diff --git a/lib/galaxy/webapps/reports/controllers/tools.py b/lib/galaxy/webapps/reports/controllers/tools.py
new file mode 100644
index 00000000000..56121836b89
--- /dev/null
+++ b/lib/galaxy/webapps/reports/controllers/tools.py
@@ -0,0 +1,365 @@
+
+import collections
+import logging
+import galaxy.model
+import sqlalchemy as sa
+
+from galaxy import util
+from galaxy.web.base.controller import BaseUIController, web
+
+from sqlalchemy import and_
+from datetime import timedelta
+from markupsafe import escape
+
+log = logging.getLogger( __name__ )
+
+
+def int_to_octet(size):
+ try:
+ size = float(size)
+ except ValueError:
+ return "???"
+ except TypeError:
+ if size is None:
+ return "0 o"
+ return "???"
+ units = ("o", "Ko", "Mo", "Go", "To")
+ no_unit = 0
+ while (size >= 1000):
+ size /= 1000.
+ no_unit += 1
+ try:
+ return "%.2f %s" % ( size, units[no_unit] )
+ except IndexError:
+ return "%.0f %s" % ( size * ( ( no_unit - len( units ) + 1 ) * 1000. ), units[-1] )
+
+
+class Tools( BaseUIController ):
+ """
+ Class defining functions used by reports to make requests to get
+ informations and fill templates before being displayed.
+ The name of function must be the same as as the field "action" of
+ the "href" dict, in .mako templates (templates/webapps/reports).
+ """
+
+ def formatted(self, date, colored=False):
+ splited = str(date).split(',')
+ if len(splited) == 2:
+ returned = "%s %dH" % (splited[0], int(splited[1].split(':')[0]))
+ if colored:
+ return '' + returned + ''
+ return returned
+ else:
+ splited = tuple([float(_) for _ in str(date).split(':')])
+ if splited[0]:
+ returned = '%d h. %d min.' % splited[:2]
+ if colored:
+ return '' + returned + ''
+ return returned
+ if splited[1]:
+ return "%d min. %d sec." % splited[1:3]
+ return "%.1f sec." % splited[2]
+
+ @web.expose
+ def tools_and_job_state( self, trans, **kwd ):
+ """
+ fill tools_and_job_state.mako template with
+ - the name of the tool
+ - the number of jobs using this tool in state 'ok'
+ - the number of jobs using this tool in error
+ """
+
+ message = escape( util.restore_text( kwd.get( 'message', '' ) ) )
+ user_cutoff = int( kwd.get( 'user_cutoff', 60 ) )
+
+ # sort by history space, or by user mail or by number of history/dataset
+ sort_by = kwd.get( 'sorting', 'Tool' )
+ sorting = 0 if sort_by == 'Tool' else 1 if sort_by == 'ok' else 2
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ sort_functions = ( lambda first, second: descending if first.lower() > second.lower() else -descending,
+ lambda first, second: -descending if tools_and_jobs_ok.get( first, 0 ) > tools_and_jobs_ok.get( second ) else descending,
+ lambda first, second: -descending if tools_and_jobs_error.get( first, 0 ) > tools_and_jobs_error.get( second, 0 ) else descending )
+
+ data = collections.OrderedDict()
+
+ # select count(id), tool_id from job where state='ok' group by tool_id;
+ tools_and_jobs_ok = sa.select( ( galaxy.model.Job.table.c.tool_id .label( 'tool' ),
+ sa.func.count( galaxy.model.Job.table.c.id ).label( 'job' ) ),
+ from_obj=[ galaxy.model.Job.table],
+ whereclause=(galaxy.model.Job.table.c.state == 'ok'),
+ group_by=[ 'tool' ] )
+
+ # select count(id), tool_id from job where state='error' group by tool_id;
+ tools_and_jobs_error = sa.select( ( galaxy.model.Job.table.c.tool_id .label( 'tool' ),
+ sa.func.count( galaxy.model.Job.table.c.id ).label( 'job' ) ),
+ from_obj=[ galaxy.model.Job.table],
+ whereclause=(galaxy.model.Job.table.c.state == 'error'),
+ group_by=[ 'tool' ] )
+
+ tools_and_jobs_ok = dict( list( tools_and_jobs_ok.execute() ) )
+ tools_and_jobs_error = dict( list( tools_and_jobs_error.execute() ) )
+
+ # select each job name one time
+ tools = list(set(tools_and_jobs_ok.keys()) | set(tools_and_jobs_error.keys()))
+ tools.sort(sort_functions[ sorting ])
+
+ for tool in tools:
+ data[tool] = (str( tools_and_jobs_ok.get(tool, '-') ), str( tools_and_jobs_error.get(tool, '-') ) )
+
+ return trans.fill_template( '/webapps/reports/tools_and_job_state.mako',
+ data=data,
+ user_cutoff=user_cutoff,
+ sorting=sorting,
+ descending=descending,
+ message=message )
+
+ @web.expose
+ def tools_and_job_state_per_month(self, trans, **kwd ):
+ """
+ fill tools_and_job_state_per_month.mako template with
+ - the name of the tool
+ - the number of jobs using this tool in state 'ok'
+ - the number of jobs using this tool in error
+ """
+
+ message = escape( util.restore_text( kwd.get( 'message', '' ) ) )
+ user_cutoff = int( kwd.get( 'user_cutoff', 60 ) )
+
+ # sort by history space, or by user mail or by number of history/dataset
+ # sort_by = kwd.get( 'sorting', 'Tool' )
+ # sorting = 0 if sort_by == 'Tool' else 1 if sort_by == 'ok' else 2
+ # descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ tool = kwd.get( 'tool', None)
+
+ if tool is None:
+ raise TypeError("Tool can't be None")
+
+ data = collections.OrderedDict()
+
+ # select count(id), create_time from job where state='ok' and tool_id=$tool group by date;
+ date_and_jobs_ok = sa.select( ( sa.func.date( galaxy.model.Job.table.c.create_time ).label( 'date' ),
+ sa.func.count( galaxy.model.Job.table.c.id ).label( 'job' ) ),
+ from_obj=[ galaxy.model.Job.table],
+ whereclause=and_( galaxy.model.Job.table.c.state == 'ok', galaxy.model.Job.table.c.tool_id == tool ),
+ group_by=[ 'date' ] )
+
+ # select count(id), create_time from job where state='error' and tool_id=$tool group by date;
+ date_and_jobs_error = sa.select( ( sa.func.date( galaxy.model.Job.table.c.create_time ).label( 'date' ),
+ sa.func.count( galaxy.model.Job.table.c.id ).label( 'job' ) ),
+ from_obj=[ galaxy.model.Job.table],
+ whereclause=and_( galaxy.model.Job.table.c.state == 'error', galaxy.model.Job.table.c.tool_id == tool ),
+ group_by=[ 'date' ] )
+
+ # sort_functions = (lambda first, second: descending if first.lower() > second.lower() else -descending,
+ # lambda first, second: -descending if tools_and_jobs_ok.get( first, 0 ) >
+ # tools_and_jobs_ok.get( second ) else descending,
+ # lambda first, second: -descending if tools_and_jobs_error.get( first, 0 ) >
+ # tools_and_jobs_error.get( second, 0 ) else descending)
+
+ date_and_jobs_ok = dict( list( date_and_jobs_ok.execute() ) )
+ date_and_jobs_error = dict( list( date_and_jobs_error.execute() ) )
+
+ # select each date
+ dates = list(set(date_and_jobs_ok.keys()) | set(date_and_jobs_error.keys()))
+ dates.sort(reverse=True)
+ for date in dates:
+ date_key = date.strftime( "%B %Y" )
+ if date_key not in data:
+ data[date_key] = [int( date_and_jobs_ok.get(date, 0) ), int( date_and_jobs_error.get(date, 0) ) ]
+ else:
+ data[date_key][0] += int( date_and_jobs_ok.get( date, 0 ) )
+ data[date_key][1] += int( date_and_jobs_error.get( date, 0 ) )
+
+ return trans.fill_template( '/webapps/reports/tools_and_job_state_per_month.mako',
+ data=data,
+ tool=tool,
+ user_cutoff=user_cutoff,
+ message=message )
+
+ @web.expose
+ def tool_execution_time(self, trans, **kwd):
+ """
+ Fill the template tool_execution_time.mako with informations:
+ - Tool name
+ - Tool average execution time
+ - last job execution time
+ - min and max execution time
+ """
+
+ # liste des tools + temps moyen d'exec du job + temps d'execution du dernier job + tps min et max / mois (?)
+ user_cutoff = int(kwd.get("user_cutoff", 60))
+ sort_by = kwd.get("sort_by", "tool")
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ sort_by = 0 if sort_by == "tool" else 1 if sort_by == "avg" else 2 if sort_by == "min" else 3
+ color = True if kwd.get("color", '') == "True" else False
+
+ data = {}
+ ordered_data = collections.OrderedDict()
+
+ def field_sort(first, second, field):
+ descending if data[first][field] < data[second][field] else -descending
+
+ sort_functions = [
+ lambda first, second: -descending if first.lower() < second.lower() else descending,
+ lambda first, second: field_sort(first, second, "avg"),
+ lambda first, second: field_sort(first, second, "min"),
+ lambda first, second: field_sort(first, second, "max")]
+
+ jobs_times = sa.select( ( galaxy.model.Job.table.c.tool_id.label( "name" ),
+ galaxy.model.Job.table.c.create_time.label("create_time"),
+ galaxy.model.Job.table.c.update_time.label("update_time"),
+ galaxy.model.Job.table.c.update_time - galaxy.model.Job.table.c.create_time ),
+ from_obj=[galaxy.model.Job.table])
+
+ jobs_times = [(name, (create, update, time)) for name, create, update, time in jobs_times.execute()]
+ for tool, attr in jobs_times:
+ if tool not in data:
+ data[tool] = { "last": [(attr[1], attr[0])], "avg": [attr[2]] }
+ else:
+ data[tool]["last"].append((attr[1], attr[0]))
+ data[tool]["avg"].append(attr[2])
+
+ for tool in data:
+ data[tool]["min"] = min(data[tool]["avg"])
+ data[tool]["max"] = max(data[tool]["avg"])
+ last = max(data[tool]["last"])
+ data[tool]["last"] = last[0] - last[1]
+ data[tool]["avg"] = sum(data[tool]["avg"], timedelta()) / len(data[tool]["avg"])
+
+ tools = data.keys()
+ if user_cutoff:
+ tools = tools[:user_cutoff]
+ tools.sort(sort_functions[sort_by])
+ for tool in tools:
+ ordered_data[tool] = { "min": self.formatted(data[tool]["min"], color),
+ "max": self.formatted(data[tool]["max"], color),
+ "avg": self.formatted(data[tool]["avg"], color),
+ "last": self.formatted(data[tool]["last"], color) }
+
+ return trans.fill_template( '/webapps/reports/tool_execution_time.mako',
+ data=ordered_data,
+ descending=descending,
+ user_cutoff=user_cutoff,
+ sort_by=sort_by )
+
+ @web.expose
+ def tool_execution_time_per_month(self, trans, **kwd):
+ """
+ Fill the template tool_execution_time_per_month.mako with informations:
+ - Tool average execution time
+ - last job execution time
+ - min and max execution time
+ """
+
+ # liste des tools + temps moyen d'exec du job + temps d'execution du dernier job + tps min et max / mois(?)
+ user_cutoff = int(kwd.get("user_cutoff", 60))
+ sort_by = kwd.get("sort_by", "month")
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ sort_by = 0 if sort_by == "month" else 1 if sort_by == "min" else 2 if sort_by == "max" else 3
+ tool = kwd.get("tool", None)
+ color = True if kwd.get("color", '') == "True" else False
+
+ if tool is None:
+ raise ValueError("Tool can't be None")
+
+ ordered_data = collections.OrderedDict()
+ sort_functions = [(lambda first, second, i=i: descending if first[i] < second[i] else -descending) for i in range(4)]
+
+ jobs_times = sa.select( ( sa.func.date_trunc('month', galaxy.model.Job.table.c.create_time ).label('date'),
+ sa.func.max(galaxy.model.Job.table.c.update_time - galaxy.model.Job.table.c.create_time),
+ sa.func.avg(galaxy.model.Job.table.c.update_time - galaxy.model.Job.table.c.create_time),
+ sa.func.min(galaxy.model.Job.table.c.update_time - galaxy.model.Job.table.c.create_time) ),
+ from_obj=[galaxy.model.Job.table],
+ whereclause=galaxy.model.Job.table.c.tool_id == tool,
+ group_by=['date'] )
+
+ months = list(jobs_times.execute())
+ months.sort(sort_functions[sort_by])
+ if user_cutoff:
+ months = months[:user_cutoff]
+
+ for month in months:
+ ordered_data[str(month[0]).split(' ')[0][:-3]] = ( self.formatted(month[1], color),
+ self.formatted(month[2], color),
+ self.formatted(month[3], color) )
+
+ return trans.fill_template( '/webapps/reports/tool_execution_time_per_month.mako',
+ data=ordered_data,
+ tool=tool,
+ descending=descending,
+ user_cutoff=user_cutoff,
+ sort_by=sort_by )
+
+ @web.expose
+ def tool_error_messages(self, trans, **kwd):
+ tool_name = kwd.get("tool", None)
+ descending = 1 if kwd.get("descending", 'desc') == "desc" else -1
+ sort_by = 0 if kwd.get("sort_by", "time") == "time" else 1
+ cutoff = int(kwd.get("user_cutoff", 60))
+ sort_functions = ( lambda _a, _b: -descending if counter[_a][1] > counter[_b][1] else descending,
+ lambda _a, _b: -descending if counter[_a][0] > counter[_b][0] else descending )
+
+ if tool_name is None:
+ raise ValueError("Tool can't be none")
+ tool_errors = [ [unicode(a), b] for a, b in
+ sa.select((galaxy.model.Job.table.c.stderr, galaxy.model.Job.table.c.create_time),
+ from_obj=[galaxy.model.Job.table],
+ whereclause=and_( galaxy.model.Job.table.c.tool_id == tool_name,
+ galaxy.model.Job.table.c.state == 'error')).execute() ]
+
+ counter = {}
+ for error in tool_errors:
+ try:
+ error[0] = unicode(error[0].decode("utf-8"))
+ # encoding tested:
+ # latin-1 ; iso-8859-1 ; alien ; cenc ; cp037 ; cp437 ; base64 ; utf-8 ; utf-16 ; ascii ; hex
+ except UnicodeEncodeError:
+ for no, lettre in enumerate(error[0]):
+ try:
+ str(lettre.decode("utf-8"))
+ except UnicodeEncodeError:
+ try:
+ error[0] = error[0].replace(error[0][no], '?')
+ except UnicodeEncodeError:
+ error[0] = "This error contains special character and can't be displayed."
+ break
+ if error[0] in counter:
+ counter[error[0]][0] += 1
+ else:
+ counter[error[0]] = [1, error[1]]
+
+ data = collections.OrderedDict()
+ keys = counter.keys()
+ if cutoff:
+ keys = keys[:cutoff]
+ keys.sort(sort_functions[sort_by])
+
+ spaces = [' ', '\t', ' ']
+ for key in keys:
+ new_key = ''.join([_ for _ in key.split('\n') if _ and _ not in spaces])
+ if len(new_key) >= 100:
+ to_replace = []
+ words = key.split('\n')
+ for word in words:
+ if word in to_replace:
+ continue
+ if words.count(word) > 1:
+ print word
+ to_replace.append(word)
+ for word in to_replace:
+ sentence = ("" + word) * 2
+ count = 2
+ while sentence + "" + word in new_key:
+ sentence += "" + word
+ count += 1
+ print sentence, count
+ if sentence in new_key:
+ new_key = new_key.replace(sentence, '' + word + " [this line in %d times]" % (count))
+ data[new_key] = counter[key]
+
+ return trans.fill_template( "/webapps/reports/tool_error_messages.mako",
+ data=data,
+ descending=descending,
+ tool_name=tool_name,
+ sort_by=sort_by,
+ user_cutoff=cutoff )
diff --git a/lib/galaxy/webapps/reports/controllers/users.py b/lib/galaxy/webapps/reports/controllers/users.py
index d373f992290..b842637fa6f 100644
--- a/lib/galaxy/webapps/reports/controllers/users.py
+++ b/lib/galaxy/webapps/reports/controllers/users.py
@@ -1,18 +1,18 @@
import calendar
import logging
import operator
-from datetime import datetime, date, timedelta
-
-from markupsafe import escape
-import sqlalchemy as sa
-from sqlalchemy import false
-
import galaxy.model
+import sqlalchemy as sa
+
from galaxy import util
from galaxy.web.base.controller import BaseUIController, web
from galaxy.webapps.reports.controllers.jobs import sorter
from galaxy.webapps.reports.controllers.query import ReportQueryBuilder
+from datetime import datetime, date, timedelta
+from markupsafe import escape
+from sqlalchemy import false
+
log = logging.getLogger( __name__ )
@@ -184,3 +184,33 @@ class Users( BaseUIController, ReportQueryBuilder ):
users=users,
user_cutoff=user_cutoff,
message=message )
+
+ @web.expose
+ def history_per_user( self, trans, **kwd ):
+ message = escape( util.restore_text( kwd.get( 'message', '' ) ) )
+ user_cutoff = int( kwd.get( 'user_cutoff', 60 ) )
+ sorting = 0 if kwd.get( 'sorting', 'User' ) == 'User' else 1
+ descending = 1 if kwd.get( 'descending', 'desc' ) == 'desc' else -1
+ sorting_functions = [
+ lambda first, second: descending if first[0].lower() > second[0].lower() else -descending,
+ lambda first, second: descending if first[1] < second[1] else -descending ]
+
+ req = sa.select(
+ ( sa.func.count( galaxy.model.History.table.c.id ).label( 'history' ),
+ galaxy.model.User.table.c.username.label( 'username' ) ),
+ from_obj=[ sa.outerjoin( galaxy.model.History.table, galaxy.model.User.table ) ],
+ whereclause=galaxy.model.History.table.c.user_id == galaxy.model.User.table.c.id,
+ group_by=[ 'username' ],
+ order_by=[ sa.desc( 'username' ), 'history' ] )
+
+ histories = [ (_.username if _.username is not None else "Unknown", _.history) for _ in req.execute() ]
+ histories.sort( sorting_functions[ sorting ] )
+ if user_cutoff != 0:
+ histories = histories[:user_cutoff]
+
+ return trans.fill_template( '/webapps/reports/history_per_user.mako',
+ histories=histories,
+ user_cutoff=user_cutoff,
+ sorting=sorting,
+ descending=descending,
+ message=message )
diff --git a/templates/webapps/reports/history_and_dataset_per_user.mako b/templates/webapps/reports/history_and_dataset_per_user.mako
new file mode 100644
index 00000000000..56de43b8971
--- /dev/null
+++ b/templates/webapps/reports/history_and_dataset_per_user.mako
@@ -0,0 +1,76 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
+
+
Histories and Datasets per User
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by
+ %if sorting == 0:
+ Users
+ %elif sorting == 1:
+ number of History
+ %elif sorting == 2:
+ number of Dataset
+ %else:
+ History Space
+ %endif
+
+
+
+ %if data:
+
+ <% odd = False%>
+ %for user in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ | ${user} |
+ ${data[user][0]} |
+ ${data[user][1]} |
+
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/history_and_dataset_type.mako b/templates/webapps/reports/history_and_dataset_type.mako
new file mode 100644
index 00000000000..e0cf8e6e713
--- /dev/null
+++ b/templates/webapps/reports/history_and_dataset_type.mako
@@ -0,0 +1,75 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
+
+
States of Datasets per History
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by History name
+
+
+
+ %if data:
+
+ <% odd = False%>
+ %for name in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ | ${name} |
+ ${data[name][0]} |
+ ${data[name][1]} |
+ ${data[name][2]} |
+ ${data[name][3]} |
+ ${data[name][4]} |
+ ${data[name][5]} |
+
+
+
+
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/history_per_user.mako b/templates/webapps/reports/history_per_user.mako
new file mode 100644
index 00000000000..4c82e822f63
--- /dev/null
+++ b/templates/webapps/reports/history_per_user.mako
@@ -0,0 +1,68 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
+
+
Histories per User
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by
+ %if sorting == 0:
+ Users
+ %else:
+ Number
+ %endif
+
+
+
+ %if histories:
+
+ <% ctr = 0 %>
+ %for history in histories:
+ %if ctr % 2 == 1:
+
+ %else:
+
+ %endif
+ | ${history[0]} |
+ ${history[1]} |
+
+ <% ctr += 1 %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/index.mako b/templates/webapps/reports/index.mako
index ff179034839..1baf5a883f8 100644
--- a/templates/webapps/reports/index.mako
+++ b/templates/webapps/reports/index.mako
@@ -9,7 +9,7 @@
%def>
<%def name="stylesheets()">
- ${parent.stylesheets()}
+ ${parent.stylesheets()}
## Include "base.css" for styling tool menu and forms (details)
${h.css( "base", "autocomplete_tagging", "tool_menu" )}
@@ -63,12 +63,23 @@
- Sample Tracking
+ Histories
+
+
+
+ Tools
+
+
@@ -93,6 +104,7 @@
+
@@ -105,8 +117,19 @@
+
+
+
+ Sample Tracking
+
+
-
+
%def>
diff --git a/templates/webapps/reports/tool_error_messages.mako b/templates/webapps/reports/tool_error_messages.mako
new file mode 100644
index 00000000000..1d94e271f74
--- /dev/null
+++ b/templates/webapps/reports/tool_error_messages.mako
@@ -0,0 +1,87 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
diff --git a/templates/webapps/reports/tool_execution_time.mako b/templates/webapps/reports/tool_execution_time.mako
new file mode 100644
index 00000000000..07e034d3159
--- /dev/null
+++ b/templates/webapps/reports/tool_execution_time.mako
@@ -0,0 +1,79 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+
+
+
+
Execution Time per Tool
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by
+ %if sort_by == 0:
+ Tool
+ %elif sort_by == 1:
+ average time
+ %elif sort_by == 2:
+ min time
+ %elif sort_by == 3:
+ max time
+ %endif
+
+
+
+ %if data:
+
+ <% odd = False%>
+ %for tool in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ |
+ ${tool}
+ |
+ ${data[tool]["avg"]} |
+ ${data[tool]["last"]} |
+ ${data[tool]["min"]} |
+ ${data[tool]["max"]} |
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/tool_execution_time_per_month.mako b/templates/webapps/reports/tool_execution_time_per_month.mako
new file mode 100644
index 00000000000..0273f2e8991
--- /dev/null
+++ b/templates/webapps/reports/tool_execution_time_per_month.mako
@@ -0,0 +1,77 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+
+
+
+
Execution Time for ${tool} per Month
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by
+ %if sort_by == 0:
+ Month
+ %elif sort_by == 1:
+ min time
+ %elif sort_by == 2:
+ max time
+ %elif sort_by == 3:
+ average time
+ %endif
+
+
+
+ %if data:
+
+ <% odd = False%>
+ %for month in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ |
+ ${month}
+ |
+ ${data[month][2]} |
+ ${data[month][0]}
+ | ${data[month][1]} |
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/tools_and_job_state.mako b/templates/webapps/reports/tools_and_job_state.mako
new file mode 100644
index 00000000000..b093f621b58
--- /dev/null
+++ b/templates/webapps/reports/tools_and_job_state.mako
@@ -0,0 +1,84 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
+
+
States of Jobs per Tool
+
Listed in
+ %if descending == 1:
+ descending
+ %else:
+ ascending
+ %endif
+ order by
+ %if sorting == 0:
+ Tool
+ %elif sorting == 1:
+ state Ok
+ %elif sorting == 2:
+ state error
+ %endif
+
+
+
+ %if data:
+
+ <% odd = False%>
+ %for tool in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ |
+ %if data[tool][0] + data[tool][1] != "--":
+ ${tool}
+ %else:
+ ${tool}
+ %endif
+ |
+ ${data[tool][0]} |
+
+ %if data[tool][1] != '-':
+ ${data[tool][1]}
+ %else:
+ -
+ %endif
+ |
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+
diff --git a/templates/webapps/reports/tools_and_job_state_per_month.mako b/templates/webapps/reports/tools_and_job_state_per_month.mako
new file mode 100644
index 00000000000..5e358a99b78
--- /dev/null
+++ b/templates/webapps/reports/tools_and_job_state_per_month.mako
@@ -0,0 +1,46 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, 'done' )}
+%endif
+
+
+
+
States of Jobs for ${tool}
+
Listed in descending by month
+
+ %if data:
+
+ <% odd = False%>
+ %for month in data:
+ %if odd:
+
+ %else:
+
+ %endif
+ | ${month} |
+
+ %if data[month][0] == 0:
+ -
+ %else:
+ ${data[month][0]}
+ %endif
+ |
+
+ %if data[month][1] == 0:
+ -
+ %else:
+ ${data[month][1]}
+ %endif
+ |
+ <% odd = not odd %>
+ %endfor
+ %endif
+
+
+