shake-0.15.1: html/shake-ui.js
/*jsl:option explicit*/
/*jsl:import shake-logic.js*/
"use strict";
// Report
// {mode :: String
// ,query :: String
// ,sort :: String
// ,sortRev :: Bool
// }
//
var defaultMode = "summary";
var defaultQuery = "";
var defaultSort = "time";
var currentTable = null;
var currentPlot = null;
/////////////////////////////////////////////////////////////////////
// GLOBAL DATA
var shakeSummary = summary(shake);
var shakeEx = prepare(shakeSummary, shake);
var report = {}; // :: Report
function reportURL(report) // :: Report -> URL
{
return "?mode=" + report.mode +
(report.query === defaultQuery ? "" : "&query=" + escape(report.query).replace(/\+/g,"%2B")) +
(report.sort === undefined || (!report.sortRev && report.sort === defaultSort) ? "" :
"&sort=" + (report.sortRev ? "!" : "") + report.sort);
}
function urlReport() // :: IO Report
{
var params = $.getParameters();
var sort = params.sort || defaultSort;
var sortRev = false;
if (sort.substr(0,1) == "!")
{
sort = sort.substr(1);
sortRev = true;
}
return {mode: params.mode || defaultMode
,query: params.query || defaultQuery
,sort: sort
,sortRev: sortRev
};
}
function enteredReport()
{
return {mode: $("#mode").val()
,query: $("#query").val()
,sort: report.sort
,sortRev: report.sortRev
};
}
function setReport(r, replace, run)
{
var changed = false;
var report2 = recordUnion(r, report);
$("#mode").val(report2.mode);
$("#query").val(report2.query);
$("#run").enable(false).attr("title", "The current query is displayed");
if (recordEq(report,report2)) return;
report = report2;
if (window.history)
{
var title = report.mode + (report.query === "" ? "" : ": " + report.query);
var url = reportURL(report);
if (replace)
window.history.replaceState(report, title, url);
else
window.history.pushState(report, title, url);
}
$("#link").attr("href", reportURL(report));
if (run)
runReport();
}
/////////////////////////////////////////////////////////////////////
// TABLE SHOWING
var rightAlign = {count:null, time:null, cost:null, run:null, leaf:null, unchanged:null};
var twoColumns = {cost:null, time:null};
var defaultRevSort = {run:null, name:null};
function tableSort(x)
{
if (report.sort === x)
setReport({sortRev: !report.sortRev}, true, false);
else
setReport({sort: x, sortRev: x in defaultRevSort}, true, false);
showTable(currentTable);
}
function showTable(xs)
{
currentTable = xs;
if (xs.length === 0)
{
$("#output").html("No data found");
return;
}
if (!(report.sort in xs[0]))
setReport({sort:defaultSort, sortRev:false}, true, false);
xs.sort(function(a,b){return (report.sortRev ? -1 : 1) * (b[report.sort] > a[report.sort] ? 1 : -1);});
var res = "<table class='data'><tr class='header'>";
for (var s in xs[0])
{
if (s === "back" || s === "text") continue;
res += s in twoColumns ? "<td colspan='2' style='text-align:center;'" :
s in rightAlign ? "<td style='text-align:right;'" :
"<td";
res += " onclick=\"tableSort('" + s + "')\">" + s;
if (s === report.sort)
res += " <span class='sort'>" + (report.sortRev ? "▲" : "▼") + "</span>";
res += "</td>";
}
res += "</tr>";
for (var i = 0; i < xs.length; i++)
{
var x = xs[i];
res += "<tr";
if (x.back) res += " style='background-color:" + x.back + ";'";
if (x.text) res += " style='color:" + x.text + ";'";
res += ">";
for (var s in xs[0])
{
if (s === "text" || s === "back") continue;
res += "<td" + (s in rightAlign ? " style='text-align:right;'" : "") + ">";
if (s === "count")
res += x[s] + " ×";
else if (s === "time" || s === "cost")
res += showTime(x[s]) + "</td><td style='text-align:right;'>" + showPerc(x[s] / shakeSummary.sumExecution);
else
res += x[s];
res += "</td>";
}
res += "</tr>";
}
res += "</table>";
$("#output").html(res);
}
/////////////////////////////////////////////////////////////////////
// SHOW A PLOT
function showPlot(series, options)
{
var $output = $("#output");
var width = $output.width();
var height = $output.height();
if (series === null && options === null)
{
if (width === currentPlot.width && height === currentPlot.height)
return;
series = currentPlot.series;
options = currentPlot.options;
}
currentPlot = {series:series, options:options, width:width, height:height};
// Fudge factors to get it displaying nicely, seems Flot goes outside its bounds
var div = $("<div>").width(width - 20).height(height - 10);
$("#output").html("").append(div);
$.plot(div, series, options);
}
window.onresize = function(){
if (currentPlot !== null)
showPlot(null, null);
};
/////////////////////////////////////////////////////////////////////
// RUNNING
function runReport()
{
currentTable = null;
currentPlot = null;
try
{
switch(report.mode)
{
case "summary":
var res = showSummary(shakeSummary);
var s = $("#welcome").html();
s += "<ul>";
for (var i = 0; i < res.length; i++)
s += "<li>" + res[i] + "</li>";
s += "</ul>";
s += "<p class='version'>Generated by <a href='http://shakebuild.com'>Shake " + version + "</a>.</p>";
$("#output").html(s);
break;
case "cmd-plot":
var xs = commandPlot(shakeEx, report.query, 100);
var ys = [];
for (var s in xs)
{
var x = xs[s].items;
var data = [];
for (var j = 0; j < x.length; j++)
data.push([j, x[j]]);
ys.push({label:s, values:x, data:data, color:xs[s].back, avg:sum(x) / x.length});
}
if (ys.length === 0)
{
$("#output").html("No data found, " +
(shakeEx.summary.countTraceLast === 0
? "there were no traced commands in the last run."
: "perhaps your filter is too restrictive?"));
}
else
{
ys.sort(function(a,b){return a.avg - b.avg;});
showPlot(ys, {
legend: {show:true, position:"nw", sorted:"reverse"},
series: {stack:true, lines:{lineWidth:0,fill:1}},
yaxis: {min:0},
xaxis: {tickFormatter: function (i){return showTime(shakeSummary.maxTraceStopLast * i / 100);}}
});
}
break;
case "cmd-table":
showTable(commandTable(shakeEx, report.query));
break;
case "rule-table":
showTable(ruleTable(shakeEx, report.query));
break;
case "rule-graph":
var xs = ruleGraph(shakeEx, report.query);
if (xs.length > 250)
$("#output").html("Viewing a graph with > 250 nodes is not supported, and you have " + xs.length + " nodes. Try grouping more aggressively");
else if (typeof Viz === 'undefined')
$("#output").html("Profile reports do not seem to have been built with GraphViz support, this feature is unavailable.");
else
{
var res = "digraph \"\"{";
res += "graph[nodesep=0.15,ranksep=0.3];";
res += "node[fontname=\"sans-serif\",fontsize=9,penwidth=0.5,height=0,width=0];";
res += "edge[penwidth=0.5,arrowsize=0.5];";
for (var i = 0; i < xs.length; i++)
{
res += "a" + i + "[label=\"" + xs[i].name.split("\\").join("\\\\").split("\"").join("\\\"") + "\"";
if (xs[i].back) res += ",style=filled,color=\"" + xs[i].back + "\"";
if (xs[i].text) res += ",fontcolor=\"" + xs[i].text + "\"";
res += "];";
var parents = xs[i].parents;
for (var j = 0; j < parents.length; j++)
res += "a" + i + "->a" + parents[j] + ";";
var ancestors = xs[i].ancestors;
for (var j = 0; j < ancestors.length; j++)
res += "a" + i + "->a" + ancestors[j] + "[style=dashed];";
}
res += "}";
$("#output").html(Viz(res,"svg"));
}
break;
case "help":
$("#output").html($("#help").html());
break;
default:
throw "Unknown report type: " + report.mode;
break;
}
}
catch (e)
{
if (!(e && e.user))
throw e;
$("#output").html($("#error").html());
for (var s in e)
$("#output ." + s).text(e[s]);
}
}
/////////////////////////////////////////////////////////////////////
// STATE NAVIGATION
function example(mode,query)
{
setReport({mode:mode, query:query, sortRev:false, sort:defaultSort}, false, true);
return false;
}
$(function(){
setReport(urlReport(), true, true);
$("#mode,#query").bind("input change",function(){
var mode = $("#mode").val();
var query = $("#query").val();
var enable = mode !== report.mode || query !== report.query;
$("#run").enable(enable).attr("title", enable ? "" : "The current query is displayed");
$("#link").attr("href", reportURL(enteredReport()));
});
$("#run").click(function(){
setReport(enteredReport(), false, true);
});
window.onpopstate = function (e){
setReport(urlReport(), true, true);
};
});
/////////////////////////////////////////////////////////////////////
// TEMPLATES
$(function(){
$("a.example").each(function(){
var mode = $(this).attr("data-mode");
var query = $(this).attr("data-query");
if (query === undefined) query = $(this).text();
var href = reportURL({mode:mode, query:query});
var onclick = "return example(unescape('" + escape(mode) + "'),unescape('" + escape(query) + "'));";
$(this).attr("href", href).attr("target","_blank")[0].setAttribute("onclick",onclick);
});
$("a.shake").each(function(){
var href = "http://hackage.haskell.org/packages/archive/shake/latest/doc/html/Development-Shake.html#v:" +
$(this).text().replace("'","-39-");
$(this).attr("href", href).attr("target","_blank");
});
});