Merge "Commit View: multiple commits, search dialog"
diff --git a/app/commit_view.html b/app/commit_view.html
index 5ecb306..800e185 100644
--- a/app/commit_view.html
+++ b/app/commit_view.html
@@ -24,7 +24,6 @@
 
 <!-- The majority of our javascript -->
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
-<script type="text/javascript" src="/js/jstree/jquery.jstree.js"></script>
   <link rel="stylesheet" type="text/css" href="/js/jquery-ui/css/custom-theme/jquery-ui-1.8.23.custom.css" />
 <script type="text/javascript" src="/js/jquery-ui/js/jquery-ui-1.8.21.custom.min.js"></script>
 <script type="text/javascript" src="/js/json2.js"></script>
@@ -39,26 +38,7 @@
 <script type="text/javascript">
 google.load('visualization', '1.0', {'packages':['corechart', 'table']});
 
-function ChartFillerCaller(input) {
-  input = input.split(',');
-  var metric = input[0];
-  var config = input[1];
-  var filename = input[2];
-  var commit = input[3];
-  var baseline = input[4];
 
-  $("#chartdialog").dialog('option', 'title', input.slice(0, 3).join(', '));
-  ChartFiller_Commit(metric, config, filename, commit, baseline, 'chartdiv',
-                     'chartdialog', null, 'configInfo', 'status');
-}
-
-function linkToBaseline(baseline) {
-  // Note: with js, there is no way to force this to open in a new tab, rather
-  // than a window.
-  var url = "/commit_viewer/" + baseline;
-  newWindow = window.open(url, '_blank');
-  newWindow.focus();
-}
 
 // These apply to all charts in commit_view.html
 var chartOptions = {width: 720,
@@ -67,7 +47,6 @@
 $(function() {
   $("#chartdialog").dialog({ autoOpen: false});
   $("#configInfo").dialog({ autoOpen: false, width: 800, position: ['right', 60]  });
-  google.load('visualization', '1.0', {'packages':['corechart', 'table']});
 
   $("#chartdialog").bind("dialogclose", function(event, ui) {
     $('#status').html("");
diff --git a/app/commit_view.py b/app/commit_view.py
index bd05634..b8b1ce2 100644
--- a/app/commit_view.py
+++ b/app/commit_view.py
@@ -26,15 +26,14 @@
 import main
 import drilldown
 import logging
+import urllib
 
 # A global variable to determine how important a test run is (percent improvement)
 THRESHOLD_HIGH = 1.0
 THRESHOLD_LOW = 0.1
 
-
-class CommitQueryHandler(webapp.RequestHandler):
-    def get(self):
-        self.response.out.write(template.render("commit_viewer.html", {}))
+# ------------------------------------------------------------------------------
+# Helpers for both handlers
 
 @cache_result()
 def get_adhoc_improvement(metrics, config, filenames, commit):
@@ -65,26 +64,144 @@
                              'filename': f, 'value': composite})
     return response
 
+def run_formatter(commit, resps):
+    '''A helper function to format the run data of a commit'''
+    formatted_resps = []
+    for row in resps:
+        if row['metric'] == 'Time(us)' or row['metric'] == 'Bitrate' or row['metric'] == 'target_bitrate':
+            continue
+        if row['filename'][0] == '~':
+            continue
+
+        if not row['baseline']:
+            row['class'] = 'unknown'
+        elif abs(row['value']) > THRESHOLD_HIGH:
+            if row['value'] > 0:
+                row['class'] = 'good major'
+            else:
+                row['class'] = 'bad major'
+
+        elif abs(row['value']) > THRESHOLD_LOW:
+            if row['value'] > 0:
+                row['class'] = 'good minor'
+            else:
+                row['class'] = 'bad minor'
+
+        else: # We are right in the middle
+            row['class'] = "unchanged"
+
+        # This is a bit messy, but it works (mixing django and
+        # javascript doesn't work like you would hope)
+        if row['baseline']:
+            row['clickcommand'] = str("javascript: ChartFillerCaller(" + "\'" +
+                                  row['metric'].encode('ascii', 'ignore') + "," +
+                                  row['config'].encode('ascii', 'ignore') + "," +
+                                  row['filename'].encode('ascii', 'ignore') + ',' +
+                                  commit['commitid'].encode('ascii', 'ignore') + "," +
+                                  row['baseline'].encode('ascii', 'ignore') + "\'"+ ')')
+        formatted_resps.append(row)
+
+    resp_rows = {}
+    for resp in formatted_resps:
+        key = (resp['metric'], resp['config'])
+        row = resp_rows.setdefault(key, [])
+        row.append(resp)
+    formatted_resps=[]
+    for key in sorted(resp_rows.keys()):
+        formatted_resps.append({
+            'metric': key[0],
+            'config': key[1],
+            'runs': sorted(resp_rows[key], key=lambda x: x['filename']),
+            })
+
+    return formatted_resps
+
+
+# ------------------------------------------------------------------------------
+
+class CommitQueryHandler(webapp.RequestHandler):
+    def get(self):
+        # We get the 5 most recent commits
+        query = model.Commit.all()
+
+        # We use this if we just want the newest 5, regardless of run data
+        #current_commits = query.order("-commit_time").fetch(limit=5)
+
+        # test data
+        current_commits = ['0030303b6949ba2d3391f3ae400213acc0e80db7',
+                           '062864f4cc2179b6f222ae337538c18bfd08037a',
+                           '05bde9d4a4b575aaadd9b6f5d0f82826b1cb4900',
+                           '0c483d6b683fa4313cf7dadf448a707fe32714a4']
+
+
+        formatted_commits = [] # These are commit_dict, formatted_resps pairs
+
+        for commit in current_commits:
+            # We get all the data about the commit we need
+            #commit_data = commit
+
+            # only for test data
+            commit_data = model.commits()[commit]
+
+            message = commit_data.message.split("\n")
+            commit = {'commit': commit_data.key().name()[:9],
+                     'commitid': commit_data.key().name(),
+                     'author': commit_data.author,
+                     'subject': message[0],
+                     'body': message[1:],
+                     'date': commit_data.author_time,
+                     'branches': commit_data.branches}
+            commitid = commit_data.key().name()
+
+            # We need (metric, config, fileset) tuples
+            resps = []
+            query = model.CodecMetricIndex.all()
+            query = query.filter('commit =', commitid)
+            for item in query:
+                resps.extend(get_adhoc_improvement(item.metrics, item.config_name,
+                                                   item.files, commitid))
+
+            # Now that we have our responses, we can format them by seeing if
+            # the value crosses our threshold
+            formatted_resps = run_formatter(commit, resps)
+
+            formatted_commits.append((commit, formatted_resps))
+
+        values = {
+            "user": users.get_current_user(),
+            "login_url": users.create_login_url("/"),
+            "logout_url": users.create_logout_url("/"),
+            "formatted_commits" : formatted_commits,
+        }
+        self.response.out.write(template.render("commit_viewer.html", values))
+
 class CommitDisplayHandler(webapp.RequestHandler):
-    def get(self, commit, optThreshold):
+    def get(self, commit):
+        commit = urllib.unquote(commit)
 
-        # TODO: will the threshold ever be adjustable?
-        if not optThreshold:
-            threshold = THRESHOLD_HIGH  # Go back to default if none given
-        else:
-            threshold = float(optThreshold)
-
-        # We start by seeing if its a valid commit
+        # We start by seeing if its a valid commit (or email address)
         indexes = model.CodecMetricIndex.all(keys_only = True)
         indexes = indexes.filter('commit =', commit)
         keys = [k.parent() for k in indexes]
         if len(keys) == 0:
-            self.error(404)
+
+            values = {
+                "user": users.get_current_user(),
+                "login_url": users.create_login_url("/"),
+                "logout_url": users.create_logout_url("/"),
+                'commit': commit,
+                'error': True,
+                'errormessage': "There are no matching results for this search.",
+            }
+
+            html = template.render("commit_view.html", values)
+            self.response.out.write(html)
+
             return
 
+        # We get all the data about the commit we need
         commit_data = model.commits()[commit]
         message = commit_data.message.split("\n")
-        nonempty_lines = sum(map(bool, message))
         commit = {'commit': commit_data.key().name()[:9],
                  'commitid': commit_data.key().name(),
                  'author': commit_data.author,
@@ -104,64 +221,23 @@
 
         # Now that we have our responses, we can format them by seeing if
         # the value crosses our threshold
-        formatted_resps = []
-        for row in resps:
-            if row['metric'] == 'Time(us)' or row['metric'] == 'Bitrate' or row['metric'] == 'target_bitrate':
-                continue
-            if row['filename'][0] == '~':
-                continue
+        formatted_resps = run_formatter(commit, resps)
 
-            if not row['baseline']:
-                row['class'] = 'unknown'
-            elif abs(row['value']) > THRESHOLD_HIGH:
-                if row['value'] > 0:
-                    row['class'] = 'good major'
-                else:
-                    row['class'] = 'bad major'
+        values = {
+            "user": users.get_current_user(),
+            "login_url": users.create_login_url("/"),
+            "logout_url": users.create_logout_url("/"),
+            'commit': commit,
+            'runs': formatted_resps
+        }
 
-            elif abs(row['value']) > THRESHOLD_LOW:
-                if row['value'] > 0:
-                    row['class'] = 'good minor'
-                else:
-                    row['class'] = 'bad minor'
-
-            else: # We are right in the middle
-              row['class'] = "unchanged"
-
-            # This is a bit messy, but it works (mixing django and
-            # javascript doesn't work like you would hope)
-            if row['baseline']:
-                row['clickcommand'] = str("javascript: ChartFillerCaller(" + "\'" +
-                                      row['metric'].encode('ascii', 'ignore') + "," +
-                                      row['config'].encode('ascii', 'ignore') + "," +
-                                      row['filename'].encode('ascii', 'ignore') + ',' +
-                                      commit['commitid'].encode('ascii', 'ignore') + "," +
-                                      row['baseline'].encode('ascii', 'ignore') + "\'"+ ')')
-            formatted_resps.append(row)
-
-        # TODO: How do we want to sort this?
-        resp_rows = {}
-        for resp in formatted_resps:
-            key = (resp['metric'], resp['config'])
-            row = resp_rows.setdefault(key, [])
-            row.append(resp)
-        formatted_resps=[]
-        for key in sorted(resp_rows.keys()):
-            formatted_resps.append({
-                'metric': key[0],
-                'config': key[1],
-                'runs': sorted(resp_rows[key], key=lambda x: x['filename']),
-                })
-
-        html = template.render("commit_view.html", {'commit': commit,
-                                                    'runs': formatted_resps,
-                                                    'threshold': threshold})
+        html = template.render("commit_view.html", values)
         self.response.out.write(html)
 
 def main_func():
     application = webapp.WSGIApplication([
         ('/commit_viewer/', CommitQueryHandler),
-        ('/commit_viewer/(.*)/(.*)', CommitDisplayHandler),
+        ('/commit_viewer/(.*)', CommitDisplayHandler),
     ], debug=True)
     webapp_util.run_wsgi_app(application)
 
diff --git a/app/commit_viewer.html b/app/commit_viewer.html
index a577e09..f2bb98a 100644
--- a/app/commit_viewer.html
+++ b/app/commit_viewer.html
@@ -26,35 +26,35 @@
     <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
   <![endif]-->
 
+<!-- The majority of our javascript -->
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
+  <link rel="stylesheet" type="text/css" href="/js/jquery-ui/css/custom-theme/jquery-ui-1.8.23.custom.css" />
+<script type="text/javascript" src="/js/jquery-ui/js/jquery-ui-1.8.21.custom.min.js"></script>
+<script type="text/javascript" src="/js/json2.js"></script>
+
+<!-- our js utils file, shared with index.html -->
+<script type="text/javascript" src="/js/utils.js"></script>
+<script type="text/javascript" src="/js/chartutils.js"></script>
+<script type="text/javascript" src="/js/commit_search.js"></script>
+
+<!-- the AJAX API for google charts -->
 <script type="text/javascript" src="https://www.google.com/jsapi"></script>
-
 <script type="text/javascript">
-
-// We load this here as well as in commit_view.html to avoid errors
 google.load('visualization', '1.0', {'packages':['corechart', 'table']});
 
+// These apply to all charts in commit_view.html
+var chartOptions = {width: 720,
+                   height: 500};
 
-function searchcommits(form){
-  var commitid = form.query.value;
+$(function() {
+  $("#chartdialog").dialog({ autoOpen: false});
+  $("#configInfo").dialog({ autoOpen: false, width: 800, position: ['right', 60]  });
 
-  // We send a request to the backend to see if it is a valid commit
-  var url = "/commit_viewer/" + commitid + '/';
-  $.ajax({
-    type: "GET",
-    url: url,
-    success: function(response){
-      $('#searchResults').html(response);
-      //$("#accordion").accordion({collapsible: true});
-      form.query.value = '';
-    },
-    error: function(xhr, ajaxOptions, thrownError) {
-      $("#searchResults").html("Not a valid commit. Try again");
-      form.query.value = '';
-    },
+  $("#chartdialog").bind("dialogclose", function(event, ui) {
+    $('#status').html("");
   });
+});
 
-}
 </script>
 
   <link rel="shortcut icon" href="/images/webm-48px.png">
@@ -110,32 +110,141 @@
 
   </header>
 
-  <div class="container-fluid">
 
+<div class="container-fluid">
+<table class="multicommits" width="100%">
+<tr><td>
     <div class="row-fluid" id="">
       <div class="span6">
+        <h3>Most Recent Commits</h3>
       </div>
       <div class="span6">
-        <a href="/" id="mainpbutton" class="btn pull-right">Back to Dashboard</a>
+        <a href="#searchModal" data-toggle="modal" id="searchbutton" class="btn pull-right">Search Commits</a>
       </div>
     </div>
+</tr></td>
+
+{% for commit, runs in formatted_commits %}
+<tr>
+<td>
 
     <div class="row-fluid" id="">
       <div class="span12">
-
-        <div id="search">
-          <div id="searchCommand"></div>
-          <form name="myform" action="" class="form-inline">
-            <input type='text' name='query' class="input-xxlarge" placeholder="Full commit hash">
-            <input type='button' id="submitButton" value="Search" onClick="javascript: searchcommits(this.form)" class="btn">
-            <span class="help-block">Example block-level help text here.</span>
-          </form>
-        </div>
-
-        <div id="searchResults"></div>
+        <h4>Commit Summary: {{ commit.commit }}</h4>
       </div>
     </div>
+
+    <div class="row-fluid" id="">
+      <div class="span6">
+
+        <table class="commitlog">
+          <tr id="{{commit.commit}}_row">
+            <td>{{ commit.commit }}</td>
+            <td>{{ commit.date }}</td>
+          </tr>
+          <tr id="{{commit.commit}}_row2">
+            <td></td>
+            <td>{{ commit.author }}</td>
+          </tr>
+          <tr id="{{commit.commit}}_row3">
+            <td></td>
+            <td>
+              <div><strong>{{ commit.subject }}</strong></div>
+              <div id="{{commit.commit}}_body1">
+                {% for line in commit.body %}
+                  {{ line }}<br>
+                {% endfor %}
+              </div>
+            </td>
+          </tr>
+          <tr id="{{commit.commit}}_row4">
+            <td></td>
+            <td>
+              <div> Branches:</div>
+              <div id="{{commit.commit}}_body2">
+                {% if commit.branches %}
+                  {% for br in commit.branches %}
+                    {{ br }}<br>
+                  {% endfor %}
+                {% else %}
+                  None
+                {% endif %}
+              </div>
+            </td>
+          </tr>
+        </table>
+      </div>
+      <div class="span6" style="overflow: auto;">
+
+        <!-- Hide the threshold for now
+        Threshold: {{threshold}}% <input type="button" value="Change" onclick="openThresholdDialog()"/>
+        <br>
+
+        <div id="thresholdDialog"> Please choose a new threshold:
+        <form id="thform" name="thform" onsubmit="submitThreshold()" style="float:left">
+        <input type='text' name='newThreshold' />
+        <input type='button' value="Submit" onclick="submitThreshold()">
+        </form>
+        </div>
+        -->
+
+        <!-- Elif statement not supported in Django 1.2 -->
+        <table class="resultgrid">
+          {% for run in runs %}
+          <tr>
+            <th>{{ run.metric }}</th>
+            <th>{{ run.config }}</th>
+            <td>
+            {% for e in run.runs %}
+              {% if e.class == 'good major' or e.class == 'good minor' %}
+              <dt class="{{e.class}}" onclick='{{e.clickcommand}}' title="{{ e.filename }}, {{e.value}}" >&#9650;</dt>
+              {% else %}
+                {% if e.class == 'bad major' or e.class == 'bad minor' %}
+                <dt class="{{e.class}}" onclick='{{e.clickcommand}}' title="{{ e.filename }}, {{e.value}}">&#9660;</dt>
+                {% else %}
+                <dt class="unchanged" onclick='{{e.clickcommand}}' title="{{ e.filename }}, {{e.value}}">&#9724;</dt>
+                {% endif %}
+              {% endif %}
+            {% endfor %}
+          {% endfor %}
+          </td>
+          </tr>
+        </table>
+      </div>
+
+    </div>
   </div>
 
+</td>
+</tr>
+{% endfor %}
+</table>
+<div> <!-- end of container fluid -->
+
+<!-- The search dialog -->
+    <div class="modal hide fade" id="searchModal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+        <h3 id="searchModalLabel">Search for Commits</h3>
+      </div>
+      <div class="modal-body">
+
+      <div id="searchCommand" style="float:left">Please enter a commit to display, or an email to look for:</div>
+      <form name="myform">
+      <input type='text' name='query' />
+      <input type='button' id="submitButton" value="Search" onClick="javascript: searchcommits(this.form)">
+      </form>
+
+      </div>
+    </div>
+
+
+<div id="chartdialog">
+  <div id="chartdiv"></div>
+  <div id="status"></div>
+</div>
+<div id="configInfo"></div>
+<script src="/js/bootstrap.min.js"></script>
+
 </body>
 </html>
diff --git a/app/js/commit_search.js b/app/js/commit_search.js
index d665d62..b00edfc 100644
--- a/app/js/commit_search.js
+++ b/app/js/commit_search.js
@@ -1,6 +1,27 @@
 // search.js
 // Contains utilities to search for a commit
 
+function ChartFillerCaller(input) {
+  input = input.split(',');
+  var metric = input[0];
+  var config = input[1];
+  var filename = input[2];
+  var commit = input[3];
+  var baseline = input[4];
+
+  $("#chartdialog").dialog('option', 'title', input.slice(0, 3).join(', '));
+  ChartFiller_Commit(metric, config, filename, commit, baseline, 'chartdiv',
+                     'chartdialog', null, 'configInfo', 'status');
+}
+
+function linkToBaseline(baseline) {
+  // Note: with js, there is no way to force this to open in a new tab, rather
+  // than a window.
+  var url = "/commit_viewer/" + baseline;
+  newWindow = window.open(url, '_blank');
+  newWindow.focus();
+}
+
 
 function searchcommits(form){