[LNT] r264723 - [ui] Add a mechanism for selecting different compare_to and baseline runs

James Molloy via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 29 05:11:28 PDT 2016


Author: jamesm
Date: Tue Mar 29 07:11:28 2016
New Revision: 264723

URL: http://llvm.org/viewvc/llvm-project?rev=264723&view=rev
Log:
[ui] Add a mechanism for selecting different compare_to and baseline runs

In the v4_runs page, we compute a run to compare to and a baseline run. It's possible for the user to pick a different run to compare to by choosing from the past 5 previous runs, but if the user wants to compare to a specific run, he has to go find that run, look up its run ID, and manually edit the URL, adding "?compare_to=ID".

A similar procedure is required for changing the baseline.

This patch adds an easier mechanism. Hovering over the table rows for "previous" or "baseline" reveals an edit icon - clicking on that launches a popover that contains a search box.

The search mechanism (runTypeahead) was taken from the profiling infrastructure and has been found a new home in lnt_run.js.

Added:
    lnt/trunk/lnt/server/ui/static/lnt_run.css
    lnt/trunk/lnt/server/ui/static/lnt_run.js
Modified:
    lnt/trunk/lnt/server/ui/profile_views.py
    lnt/trunk/lnt/server/ui/static/lnt_profile.js
    lnt/trunk/lnt/server/ui/templates/reporting/runs.html
    lnt/trunk/lnt/server/ui/templates/v4_profile.html
    lnt/trunk/lnt/server/ui/templates/v4_run.html
    lnt/trunk/lnt/server/ui/views.py

Modified: lnt/trunk/lnt/server/ui/profile_views.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/profile_views.py?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/profile_views.py (original)
+++ lnt/trunk/lnt/server/ui/profile_views.py Tue Mar 29 07:11:28 2016
@@ -173,7 +173,7 @@ def v4_profile(testid, run1_id, run2_id=
     else:
         json_run2 = {}
     urls = {
-        'search': v4_url_for('v4_profile_search'),
+        'search': v4_url_for('v4_search'),
         'singlerun_template': v4_url_for('v4_profile_fwd',
                                           testid=1111,
                                           run1_id=2222) \
@@ -194,64 +194,3 @@ def v4_profile(testid, run1_id, run2_id=
                            ts=ts, test=test,
                            run1=json_run1, run2=json_run2,
                            urls=urls)
-
- at v4_route("/profile/search")
-def v4_profile_search():
-    def _isint(i):
-        try:
-            int(i)
-            return True
-        except:
-            return False
-
-    ts = request.get_testsuite()
-    query = request.args.get('q')
-    l = request.args.get('l', 8)
-    #default_machine = request.args.get('m')
-
-    machine_queries = []
-    order_query = None
-    for q in query.split(' '):
-        if not q:
-            continue
-        if q.startswith('#'):
-            order_query = q[1:]
-        elif _isint(q):
-            order_query = q
-        else:
-            machine_queries.append(q)
-
-    if not machine_queries and order_query is None:
-        return "{}"
-
-    if machine_queries:
-        machines = []
-        for m in ts.query(ts.Machine).all():
-            if all(q in m.name for q in machine_queries):
-                machines.append(m.id)
-        if not machines:
-            return "{}"
-    else:
-        # FIXME:
-        return "{}"
-
-    q = ts.query(ts.Run).filter(ts.Run.machine_id.in_(machines))
-    if order_query:
-        # FIXME: Is this generating a million billion queries under my feet?
-        # I hate ORMs :( I know this column exists, but because it's
-        # dynamically generated SQLAlchemy can't filter on it.
-        # Perhaps there's a way to provide it a manual column name?
-        # I want to do this:
-        # filter(ts.Run.order.llvm_project_revision.like('%' + order_query + '%'))
-        oq = str(order_query)
-        data = [i
-                for i in q.all()
-                if oq in str(i.order.llvm_project_revision)]
-
-    else:
-        data = q.limit(l).all()
-
-    return json.dumps(
-        [('%s #%s' % (r.machine.name, r.order.llvm_project_revision),
-          r.id)
-         for r in data])

Modified: lnt/trunk/lnt/server/ui/static/lnt_profile.js
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_profile.js?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_profile.js (original)
+++ lnt/trunk/lnt/server/ui/static/lnt_profile.js Tue Mar 29 07:11:28 2016
@@ -348,101 +348,6 @@ ToolBar.prototype = {
     }
 };
 
-function RunTypeahead(element, options) {
-    this.element = element;
-    this.options = options;
-    this.id = null;
-
-    this_ = this;
-    element.typeahead({
-        source: function(query, process) {
-            $.ajax(options.searchURL, {
-                dataType: "json",
-                data: {'q': query},
-                success: function(data) {
-                    process(data);
-                },
-                error: function(xhr, textStatus, errorThrown) {
-                    pf_flash_error('accessing URL ' + options.searchURL +
-                                   '; ' + errorThrown);
-                }
-            });
-        },
-        // These identity functions are required because the defaults
-        // assume items are strings, whereas they're objects here.
-        sorter: function(items) {
-            // The results should be sorted on the server.
-            return items;
-        },
-        matcher: function(item) {
-            return item;
-        },
-        updater: function(item) {
-            // FIXME: the item isn't passed in as json any more, it's
-            // been rendered. Lame. To get around this, hack the
-            // components of the 2-tuple back apart.
-            name = item.split(',')[0];
-            id = item.split(',')[1];
-            this_.id = id;
-            
-            if (options.updated)
-                options.updated(name, id);
-            return name;
-        },
-        highlighter: function(item) {
-            // item is a 2-tuple [name, obj].
-            item = item[0];
-
-            // This loop highlights each search term (split by space)
-            // individually. The content of the for loop is lifted from
-            // bootstrap.js (it's the original implementation of
-            // highlighter()). In particular I have no clue what that regex
-            // is doing, so don't bother asking.
-            var arr = this.query.split(' ');
-            for (i in arr) {
-                query = arr[i];
-                if (!query)
-                    continue;
-                var q = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,
-                                          '\\$&')
-                item = item.replace(new RegExp('(' + q + ')', 'ig'), function ($1, match) {
-                    // We want to replace with <strong>match</strong here,
-                    // but it's possible another search term will then
-                    // match part of <strong>.
-                    //
-                    // Therefore, replace with two replaceable tokens that
-                    // no search query is very likely to match...
-                    return '%%%' + match + '£££'
-                });
-            }
-            return item
-                .replace(/%%%/g, '<strong>')
-                .replace(/£££/g, '</strong>');
-        }
-    });
-    // Bind an event disabling the function box and removing the profile
-    // if the run box is emptied.
-    element.change(function() {
-        if (!element.val()) {
-            this_.id = null;
-            if (options.cleared)
-                options.cleared();
-        }
-    });
-}
-
-RunTypeahead.prototype = {
-    update: function (name, id) {
-        this.element.val(name);
-        this.id = id;
-        if (this.options.updated)
-            this.options.updated(name, id);
-    },
-    getSelectedRunId: function() {
-        return this.id;
-    }
-};
-
 function FunctionTypeahead(element, options) {
     this.element = element;
     this.options = options;
@@ -623,12 +528,6 @@ $(document).ready(function () {
             
             return this.data('toolBar');
         },
-        runTypeahead: function(options) {
-            if (options)
-                this.data('runTypeahead',
-                          new RunTypeahead(this, options));
-            return this.data('runTypeahead');
-        },
         functionTypeahead: function(options) {
             if (options)
                 this.data('functionTypeahead',

Added: lnt/trunk/lnt/server/ui/static/lnt_run.css
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_run.css?rev=264723&view=auto
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_run.css (added)
+++ lnt/trunk/lnt/server/ui/static/lnt_run.css Tue Mar 29 07:11:28 2016
@@ -0,0 +1,20 @@
+.editable {
+    position: absolute;
+    background-color: black;
+    right: 0px;
+    top: 0px;
+    bottom: 0px;
+    width: 25px;
+    opacity: 0.5;
+    text-align: center;
+    visibility: hidden;
+}
+
+tr:hover > td > .editable {
+    visibility: visible;
+    cursor: pointer;
+}
+
+.editable span {
+    position: relative;
+}
\ No newline at end of file

Added: lnt/trunk/lnt/server/ui/static/lnt_run.js
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/static/lnt_run.js?rev=264723&view=auto
==============================================================================
--- lnt/trunk/lnt/server/ui/static/lnt_run.js (added)
+++ lnt/trunk/lnt/server/ui/static/lnt_run.js Tue Mar 29 07:11:28 2016
@@ -0,0 +1,190 @@
+function RunTypeahead(element, options) {
+    this.element = element;
+    this.options = options;
+    this.id = null;
+
+    this_ = this;
+    element.typeahead({
+        source: function(query, process) {
+            $.ajax(options.searchURL, {
+                dataType: "json",
+                data: $.extend({}, this_.options.data, {'q': query}),
+                success: function(data) {
+                    process(data);
+                },
+                error: function(xhr, textStatus, errorThrown) {
+                    pf_flash_error('accessing URL ' + options.searchURL +
+                                   '; ' + errorThrown);
+                }
+            });
+        },
+        // These identity functions are required because the defaults
+        // assume items are strings, whereas they're objects here.
+        sorter: function(items) {
+            // The results should be sorted on the server.
+            return items;
+        },
+        matcher: function(item) {
+            return item;
+        },
+        updater: function(item) {
+            // FIXME: the item isn't passed in as json any more, it's
+            // been rendered. Lame. To get around this, hack the
+            // components of the 2-tuple back apart.
+            name = item.split(',')[0];
+            id = item.split(',')[1];
+            this_.id = id;
+            
+            if (options.updated)
+                options.updated(name, id);
+            return name;
+        },
+        highlighter: function(item) {
+            // item is a 2-tuple [name, obj].
+            item = item[0];
+
+            // This loop highlights each search term (split by space)
+            // individually. The content of the for loop is lifted from
+            // bootstrap.js (it's the original implementation of
+            // highlighter()). In particular I have no clue what that regex
+            // is doing, so don't bother asking.
+            var arr = this.query.split(' ');
+            for (i in arr) {
+                query = arr[i];
+                if (!query)
+                    continue;
+                var q = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,
+                                          '\\$&')
+                item = item.replace(new RegExp('(' + q + ')', 'ig'), function ($1, match) {
+                    // We want to replace with <strong>match</strong here,
+                    // but it's possible another search term will then
+                    // match part of <strong>.
+                    //
+                    // Therefore, replace with two replaceable tokens that
+                    // no search query is very likely to match...
+                    return '%%%' + match + '£££'
+                });
+            }
+            return item
+                .replace(/%%%/g, '<strong>')
+                .replace(/£££/g, '</strong>');
+        }
+    });
+    // Bind an event disabling the function box and removing the profile
+    // if the run box is emptied.
+    element.change(function() {
+        if (!element.val()) {
+            this_.id = null;
+            if (options.cleared)
+                options.cleared();
+        }
+    });
+}
+
+RunTypeahead.prototype = {
+    update: function (name, id) {
+        this.element.val(name);
+        this.id = id;
+        if (this.options.updated)
+            this.options.updated(name, id);
+    },
+    getSelectedRunId: function() {
+        return this.id;
+    }
+};
+
+function modifyURL(type, id) {
+    var url = window.location.href;
+
+    if (type == 'Current') {
+        return url.replace(/\d+($|\?)/, id + '?');
+    }
+    if (type == 'Previous') {
+        if (url.indexOf('compare_to=') != -1)
+            return url.replace(/compare_to=\d+/, 'compare_to=' + id);
+        else if (url.indexOf('?') != -1)
+            return url + '&compare_to=' + id;
+        else
+            return url + '?compare_to=' + id;
+    }
+    if (type == 'Baseline') {
+        if (url.indexOf('baseline=') != -1)
+            return url.replace(/baseline=\d+/, 'baseline=' + id);
+        else if (url.indexOf('?') != -1)
+            return url + '&baseline=' + id;
+        else
+            return url + '?baseline=' + id;
+    }
+}
+
+function makeEditable(type, td) {
+    var exterior = $('<span></span>');
+
+    var content = $('<input type="text">')
+        .addClass("input-large");
+    
+    var title = $('<b></b>')
+        .text('Choose run...')
+        .append($('<span>×</span>')
+                .addClass('close'));
+
+    var enableTypeahead = function() {
+        content.runTypeahead({
+            searchURL: g_urls.search,
+            data: {'m': g_machine},
+            updated: function(name, id) {
+                var url = modifyURL(type, id);
+                document.location.href = url;
+            },
+            cleared: function()  {
+                console.log("jjg");
+            }
+        });
+        title.children('span').first().click(function() {
+            exterior.popover('hide');
+        });
+    }
+
+    var h = td.height() / 2;
+    
+    var interior = $('<span></span>')
+        .css({marginTop: (h - 6) + 'px'})
+        .addClass('icon-pencil icon-white');
+
+    exterior.addClass('editable')
+        .append(interior)
+        .popover({
+            title: title,
+            content: content,
+            html: true,
+            trigger: 'click'
+        }).on('shown.bs.popover', function(){
+            enableTypeahead();
+        });
+    
+    td.css({position: 'relative'})
+        .append(exterior);
+}
+
+$(function() {
+
+    jQuery.fn.extend({
+        runTypeahead: function(options) {
+            if (options)
+                this.data('runTypeahead',
+                          new RunTypeahead(this, options));
+            return this.data('runTypeahead');
+        }
+    });
+    
+    makeEditable( 'Current', $('#run-table-Current')
+                  .children('td')
+                  .last());
+    makeEditable( 'Previous', $('#run-table-Previous')
+                  .children('td')
+                  .last());
+    makeEditable( 'Baseline', $('#run-table-Baseline')
+                  .children('td')
+                  .last());
+
+});

Modified: lnt/trunk/lnt/server/ui/templates/reporting/runs.html
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/templates/reporting/runs.html?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/templates/reporting/runs.html (original)
+++ lnt/trunk/lnt/server/ui/templates/reporting/runs.html Tue Mar 29 07:11:28 2016
@@ -96,7 +96,7 @@
   <tbody>
   {% for title, r in (('Current', run), ('Previous', compare_to), ('Baseline', baseline)) %}
     {% if r %}
-    <tr>
+    <tr id="run-table-{{ title }}">
       <td style="{{ styles['td'] }}"><a href="{{ ts_url }}/{{ r.id }}">{{ title }}</a></td>
       <td style="{{ styles['td'] }}"><a href="{{ ts_url }}/order/{{ r.order.id }}">{{ r.order.llvm_project_revision }}</a></td>
       <td style="{{ styles['td'] }}"><span class="utctime">{{ r.start_time.isoformat() }}</span></td>
@@ -106,7 +106,9 @@
       {% endif %}
     </tr>
     {% else %}
-      <tr><td style="{{ styles['td'] }}" colspan="5">No {{ title }} Run</td></tr>
+    <tr id="run-table-{{ title }}">
+      <td style="{{ styles['td'] }}" colspan="5">No {{ title }} Run</td>
+    </tr>
     {% endif %}
   {% endfor %}
  </tbody>

Modified: lnt/trunk/lnt/server/ui/templates/v4_profile.html
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/templates/v4_profile.html?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/templates/v4_profile.html (original)
+++ lnt/trunk/lnt/server/ui/templates/v4_profile.html Tue Mar 29 07:11:28 2016
@@ -7,6 +7,9 @@
 {% block head %}
   <script language="javascript" type="text/javascript"
           src="{{ url_for('.static',
+               filename='lnt_run.js') }}"></script>
+  <script language="javascript" type="text/javascript"
+          src="{{ url_for('.static',
                filename='lnt_profile.js') }}"></script>
   <script language="javascript" type="text/javascript"
           src="{{ url_for('.static',

Modified: lnt/trunk/lnt/server/ui/templates/v4_run.html
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/templates/v4_run.html?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/templates/v4_run.html (original)
+++ lnt/trunk/lnt/server/ui/templates/v4_run.html Tue Mar 29 07:11:28 2016
@@ -15,8 +15,16 @@
                      v4_url_for("v4_machine", id=machine.id))] %}
 
 {% block head %}
+  <script>
+    g_urls = {{urls|tojson|safe}};
+    g_machine = {{run.machine.id}};
+  </script>
+
   <script src="{{ url_for('.static', filename='popup.js') }}"></script>
   <script src="{{ url_for('.static', filename='sorttable.js') }}"></script>
+  <script src="{{ url_for('.static', filename='lnt_run.js') }}"></script>
+  <link href="{{ url_for('.static', filename='lnt_run.css') }}" rel="stylesheet" media="screen"/>
+
   <script type="text/javascript">
   function selectAll(source) {
     $(source).closest("table").find("input:checkbox").prop("checked", source.checked);

Modified: lnt/trunk/lnt/server/ui/views.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/views.py?rev=264723&r1=264722&r2=264723&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/views.py (original)
+++ lnt/trunk/lnt/server/ui/views.py Tue Mar 29 07:11:28 2016
@@ -4,6 +4,7 @@ import re
 import tempfile
 import time
 import copy
+import json
 
 import flask
 from flask import abort
@@ -30,6 +31,7 @@ import lnt.server.ui.util
 import lnt.server.reporting.dailyreport
 import lnt.server.reporting.summaryreport
 import lnt.server.db.rules_manager
+import lnt.server.db.search
 from collections import namedtuple
 from lnt.util import async_ops
 
@@ -417,12 +419,16 @@ def v4_run(id):
 
         return flask.jsonify(**json_obj)
 
+    urls = {
+        'search': v4_url_for('v4_search')
+    }
     return render_template(
         "v4_run.html", ts=ts, options=options,
         metric_fields=list(ts.Sample.get_metric_fields()),
         test_info=test_info, analysis=lnt.server.reporting.analysis,
         test_min_value_filter=test_min_value_filter,
-        request_info=info)
+        request_info=info, urls=urls
+)
 
 @v4_route("/order/<int:id>")
 def v4_order(id):
@@ -1233,3 +1239,26 @@ def health():
     if explode:
         return msg, 500
     return msg, 200
+
+ at v4_route("/search")
+def v4_search():
+    def _isint(i):
+        try:
+            int(i)
+            return True
+        except:
+            return False
+
+    ts = request.get_testsuite()
+    query = request.args.get('q')
+    l = request.args.get('l', 8)
+    default_machine = request.args.get('m', None)
+
+    assert query
+    results = lnt.server.db.search.search(ts, query, num_results=l,
+                                          default_machine=default_machine)
+
+    return json.dumps(
+        [('%s #%s' % (r.machine.name, r.order.llvm_project_revision),
+          r.id)
+         for r in results])




More information about the llvm-commits mailing list