[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