Add reporting support for backend.

From: Radha Ramachandran <radha@google.com>
Signed-off-by: Martin Bligh <mbligh@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@880 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/tko/compose_query.cgi b/tko/compose_query.cgi
new file mode 100644
index 0000000..2eba013
--- /dev/null
+++ b/tko/compose_query.cgi
@@ -0,0 +1,141 @@
+#!/usr/bin/python
+
+"""
+Selects all rows and columns that satisfy the condition specified
+and draws the matrix. There is a seperate SQL query made for every (x,y)
+in the matrix.
+"""
+
+
+print "Content-type: text/html\n"
+import cgi, cgitb, re
+import sys
+
+tko = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+sys.path.insert(0, tko)
+
+import display, frontend, db
+
+cgitb.enable()
+db = db.db()
+
+def generate_sql_condition(condition_list):
+	""" generate the sql for the condition list."""
+	sql = ""
+	value = []
+	for field, operator, values in condition_list:
+		if len(values) == 1:
+			sql += " and %s%s%%s" % (field, operator)
+			value.append(values[0][0])
+		elif len(values) > 1:
+			sql += " and "
+			expression = [" %s %s %%s" % (field, operator) for val in values]
+			for val in values:
+				value.append(val[0])
+			sql += "(%s)" % " or ".join(expression)
+	return sql, value
+
+
+def prune_list(thelist, condition_sql, condition_value):
+	""" keep track of which columns do not have any elements."""
+	pruned_list = []
+	for g in thelist:
+		sql = "t where %s=%%s " % g.idx_name
+		value = [g.idx_value]
+		sql += condition_sql
+		value.extend(condition_value)
+		tests = frontend.test.select_sql(db, sql, value)
+		if len(tests) > 0:
+			pruned_list.append(g)
+	return pruned_list
+
+
+def ParseCondition(condition):
+	""" parse the condition into independent clauses."""
+	condition_list = []
+	if not condition:
+		return condition_list
+	attribute_re = r"(\w+)"
+	op_re = r"(=|!=)"
+	value_re = r"('[^']*')"
+	# condition is clause & clause & ..
+	clause_re = r"%s\s*%s\s*%s" % (attribute_re, op_re, value_re)
+	condition_re = re.compile(r"^\s*%s(\s*&\s*%s)*\s*$" % (clause_re, clause_re))
+	if not condition_re.match(condition):
+		print "Condition not in the correct format: %s" % condition
+		sys.exit(0)
+	triples = []
+	for clause in [c.strip() for c in condition.split('&')]:
+		attribute, op, value = re.match(clause_re, clause).groups()
+		triples.append((attribute, op, value))
+	for (field_name, operator, value) in triples:
+		match, field = frontend.select(db, field_name, value)
+		if len(match) > 0:
+			condition_list.append((field, operator, match))
+		else:
+			print "No matching records found for condition %s." % \
+			      condition
+			sys.exit(0)
+	return condition_list
+
+
+def main():
+
+	# parse the fields from the form.
+	form = cgi.FieldStorage()
+	columns = 'kernel'
+	rows = 'test'
+	condition = None
+	for field in form:
+		value = form[field].value
+		if field == 'columns':
+			columns = value
+		elif field == 'rows':
+			rows = value
+		elif field == 'condition':
+			condition = value
+
+	# parse the conditions into sql query and value list.
+	condition_sql = ""
+	condition_value = []
+	if condition:
+		condition_list = ParseCondition(condition)
+		condition_sql, condition_value = generate_sql_condition(condition_list)
+
+	# get all possible column values.
+	column_groups = frontend.anygroup.selectunique(db, columns)
+
+	# get all possible row values.
+	row_groups = frontend.anygroup.selectunique(db,rows)
+	# keep only those values in rows/columns that have a test
+	# corresponding to it.
+	row_groups = prune_list(row_groups, condition_sql, condition_value)
+	column_groups = prune_list(column_groups, condition_sql, condition_value)
+
+	# prepare the header for the table.
+	headers = [g.name for g in column_groups]
+
+	header_row = [display.box(x, header=True) for x in headers]
+	header_row.insert(0, display.box("", header=True))
+
+	matrix = [header_row]
+
+	for r_group in row_groups:
+		row = [display.box(r_group.name)]
+		# get individual unit values
+		for c_group in column_groups:
+			sql = "t where %s=%%s and %s=%%s" % (r_group.idx_name,
+							     c_group.idx_name)
+			value = [r_group.idx_value, c_group.idx_value]
+			sql += condition_sql
+			value.extend(condition_value)
+			tests = frontend.test.select_sql(db, sql, value)
+			value_str = [str(val) for val in value]
+			link = 'test.cgi?sql=%s&values=%s' % \
+				(sql, ','.join(value_str))
+			row.append(display.status_count_box(db, tests, link))
+		matrix.append(row)
+	display.print_table(matrix)
+
+
+main()
diff --git a/tko/frontend.py b/tko/frontend.py
index fc05e2f..22e6be9 100755
--- a/tko/frontend.py
+++ b/tko/frontend.py
@@ -8,6 +8,62 @@
 else:
         html_root = '/results/'
 
+def select(db, field, value=None, distinct=False):
+	""" returns the relevant index values where the field value matches the
+	    input value to the function.
+	    If there is no value passed, then it returns the index values and the
+	    field values corresponsing to them. """
+
+	fields = { 'kernel': ['printable', 'kernel_idx', 'kernel_idx'],
+	 	   'machine_group': ['machine_group', 'machine_idx', 'machine_idx'],
+		   'hostname': ['hostname', 'machine_idx', 'machine_idx'],
+		   'label': ['label', 'job_idx', 'job_idx'],
+		   'user': ['user', 'job_idx', 'job_idx'],
+		   'test': ['test', 'test', 'test'],
+		   'status': ['word', 'status_idx', 'status'],
+		   'reason': ['reason', 'test_idx', 'test_idx'] }
+	table = { 'kernel': 'kernels',
+		  'machine_group': 'machines',
+		  'hostname': 'machines',
+		  'label': 'jobs',
+		  'user': 'jobs',
+		  'test': 'tests',
+		  'status': 'status',
+		  'reason': 'tests' }
+
+	lookup_field, idx_field, field_name_in_main_table = fields[field]
+	tablename = table[field]
+	# select all the index values that match the given field name.
+	sql = ""
+	if distinct:
+		sql += " distinct "
+	if not value:
+		sql += " %s , %s " % (lookup_field, idx_field)
+		where = " %s is not null " % lookup_field
+	else:
+		sql += "%s " % idx_field
+		where = " %s = %s " % (lookup_field, value)
+
+	match = db.select(sql, tablename, where)
+	# returns the value and its field name
+	return match, field_name_in_main_table
+
+class anygroup:
+	@classmethod
+	def selectunique(klass, db, field):
+		"""Return unique values for all possible groups within
+		 the table."""
+		rows, field_name_in_main_table = select(db, field, value=None, distinct=True)
+		groupnames = sorted([row for row in rows])
+		return [klass(db, field_name_in_main_table, groupname) for groupname in groupnames]
+
+
+	def __init__(self, db, idx_name, name):
+		self.db = db
+		self.name = name[0]
+		self.idx_name = idx_name
+		self.idx_value = name[1]
+
 
 class group:
 	@classmethod
@@ -17,8 +73,7 @@
 						'machine_group is not null')
 		groupnames = sorted([row[0] for row in rows])
 		return [klass(db, groupname) for groupname in groupnames]
-
-
+	
 	def __init__(self, db, name):
 		self.name = name
 		self.db = db
@@ -26,7 +81,7 @@
 
 	def machines(self):
 		return machine.select(self.db, { 'machine_group' : self.name })
-	
+
 
 	def tests(self, where = {}):
 		values = [self.name]
@@ -36,7 +91,7 @@
 			values.append(where[key])
 		return test.select_sql(self.db, sql, values)
 
-	
+
 class machine:
 	@classmethod
 	def select(klass, db, where = {}):
@@ -53,7 +108,7 @@
 		self.hostname = hostname
 		self.group = group
 		self.owner = owner
-		
+
 
 class kernel:
 	@classmethod
diff --git a/tko/index.html b/tko/index.html
new file mode 100644
index 0000000..ca5b94b
--- /dev/null
+++ b/tko/index.html
@@ -0,0 +1,68 @@
+<html>
+<head>
+</head>
+<body>
+<table border="0">
+  <form action="compose_query.cgi" method="get">
+<tr>
+  <td>Column: </td>
+  <td>
+  <SELECT NAME="columns">
+  <OPTION VALUE="kernel">kernel
+  <OPTION VALUE="hostname">hostname
+  <OPTION VALUE="user">user
+  <OPTION VALUE="test">test
+  <OPTION VALUE="label">label
+  <OPTION VALUE="machine_group">machine_group
+  <OPTION VALUE="status">status
+  <OPTION VALUE="reason">reason
+  </SELECT>
+  </td>
+</tr>
+<tr>
+  <td>Row: </td>
+  <td>
+  <SELECT NAME="rows">
+  <OPTION VALUE="test">test
+  <OPTION VALUE="kernel">kernel
+  <OPTION VALUE="hostname">hostname
+  <OPTION VALUE="user">user
+  <OPTION VALUE="label">label
+  <OPTION VALUE="machine_group">machine_group
+  <OPTION VALUE="status">status
+  <OPTION VALUE="reason">reason
+  </SELECT>
+  </td>
+</tr>
+<tr>
+  <td>Condition: </td>
+  <td>
+    <input type="text" name="condition" size="80" maxlength="80"><br />
+    <input type="hidden" name="title" value="Report">
+  </td>
+</tr>
+<tr>
+  <td colspan="100%" align="center"><input type="submit" value="Submit">
+  </td>
+</tr>
+<tr>
+  <td colspan="100%">
+<p>
+Conditions of the form &lt;column&gt;=&lt;value&gt; &amp; &lt;column&gt;=&lt;value&gt; &amp; ... &amp; &lt;column&gt;=&lt;value&gt;
+</p>
+<p>
+Textual values must be quoted.
+</p>
+<p>
+<div>Examples:</div>
+<ul>
+<li>user='johnmacdonald' & test='burnin'</li>
+<li>hostname='bdpk1' & user='yinghan'</li>
+</ul>
+</p>
+  </td>
+</tr>
+</form>
+</table>
+</body>
+</html>
diff --git a/tko/test.cgi b/tko/test.cgi
new file mode 100644
index 0000000..dd858b0
--- /dev/null
+++ b/tko/test.cgi
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+"""
+Further display the tests in a matrix of the form tests X machines
+to help understand the results selected from the previous form.
+"""
+
+print "Content-type: text/html\n"
+import cgi, cgitb, os, sys, re
+sys.stdout.flush()
+cgitb.enable()
+
+tko = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
+sys.path.insert(0, tko)
+import db, display, frontend
+
+db = db.db()
+
+def main():
+	form = cgi.FieldStorage()
+
+	if form.has_key('sql'):
+		sql = form['sql'].value
+
+	if form.has_key('values'):
+		values = [val for val in form['values'].value.split(',')]
+
+	if not sql:
+		return
+	if not values:
+		return
+
+	tests = frontend.test.select_sql(db, sql, values)
+
+	# get the list of tests/machines to populate the row and column header.
+	testname = [test.testname for test in tests]
+	machine_idx = [test.machine_idx for test in tests]
+
+	# We dont want repetitions in the table row/column headers,
+	# so eliminate the dups.
+	uniq_test = list(set(testname))
+	uniq_machine_idx = list(set(machine_idx))
+
+	header_row = [ display.box('', header = True) ]
+	for test_name in uniq_test:
+		header_row.append(display.box(test_name, header=True))
+	matrix = [header_row]
+	for machine in uniq_machine_idx:
+		mach_name = db.select_sql('hostname', 'machines',
+				 ' where machine_idx=%s', [str(machine)])
+		row = [display.box(mach_name[0][0])]
+		for test_name in uniq_test:
+			testlist = [test for test in tests
+					 if test.machine_idx == machine
+					 and test.testname == test_name]
+			# url link to the first test.
+			# TODO: provide another level to show the different
+			#	test results.
+			link = None
+			if testlist and testlist[0]:
+				link = testlist[0].url
+			box = display.status_count_box(db, testlist, link=link)
+			row.append(box)
+		matrix.append(row)
+	display.print_table(matrix)
+
+main()
+