1*2abb3134SXin Li// Dashboard UI functions. 2*2abb3134SXin Li// 3*2abb3134SXin Li// This is shared between all HTML pages. 4*2abb3134SXin Li 5*2abb3134SXin Li'use strict'; 6*2abb3134SXin Li 7*2abb3134SXin Li// Append a message to an element. Used for errors. 8*2abb3134SXin Lifunction appendMessage(elem, msg) { 9*2abb3134SXin Li elem.innerHTML += msg + '<br />'; 10*2abb3134SXin Li} 11*2abb3134SXin Li 12*2abb3134SXin Li// jQuery-like AJAX helper, but simpler. 13*2abb3134SXin Li 14*2abb3134SXin Li// Requires an element with id "status" to show errors. 15*2abb3134SXin Li// 16*2abb3134SXin Li// Args: 17*2abb3134SXin Li// errElem: optional element to append error messages to. If null, then 18*2abb3134SXin Li// alert() on error. 19*2abb3134SXin Li// success: callback that is passed the xhr object. 20*2abb3134SXin Lifunction ajaxGet(url, errElem, success) { 21*2abb3134SXin Li var xhr = new XMLHttpRequest(); 22*2abb3134SXin Li xhr.open('GET', url, true /*async*/); 23*2abb3134SXin Li xhr.onreadystatechange = function() { 24*2abb3134SXin Li if (xhr.readyState != 4 /*DONE*/) { 25*2abb3134SXin Li return; 26*2abb3134SXin Li } 27*2abb3134SXin Li 28*2abb3134SXin Li if (xhr.status != 200) { 29*2abb3134SXin Li var msg = 'ERROR requesting ' + url + ': ' + xhr.status + ' ' + 30*2abb3134SXin Li xhr.statusText; 31*2abb3134SXin Li if (errElem) { 32*2abb3134SXin Li appendMessage(errElem, msg); 33*2abb3134SXin Li } else { 34*2abb3134SXin Li alert(msg); 35*2abb3134SXin Li } 36*2abb3134SXin Li return; 37*2abb3134SXin Li } 38*2abb3134SXin Li 39*2abb3134SXin Li success(xhr); 40*2abb3134SXin Li }; 41*2abb3134SXin Li xhr.send(); 42*2abb3134SXin Li} 43*2abb3134SXin Li 44*2abb3134SXin Li// Load metadata about the metrics. 45*2abb3134SXin Li// metric-metadata.json is just 14 KB, so we load it for every page. 46*2abb3134SXin Li// 47*2abb3134SXin Li// callback: 48*2abb3134SXin Li// on metric page, just pick out the right description. 49*2abb3134SXin Li// on overview page, populate them ALL with tool tips? 50*2abb3134SXin Li// Or create another column? 51*2abb3134SXin Lifunction loadMetricMetadata(errElem, success) { 52*2abb3134SXin Li // TODO: Should we make metric-metadata.json optional? Some may not have it. 53*2abb3134SXin Li 54*2abb3134SXin Li ajaxGet('metric-metadata.json', errElem, function(xhr) { 55*2abb3134SXin Li // TODO: handle parse error 56*2abb3134SXin Li var m = JSON.parse(xhr.responseText); 57*2abb3134SXin Li success(m); 58*2abb3134SXin Li }); 59*2abb3134SXin Li} 60*2abb3134SXin Li 61*2abb3134SXin Li// for overview.html. 62*2abb3134SXin Lifunction initOverview(urlHash, tableStates, statusElem) { 63*2abb3134SXin Li 64*2abb3134SXin Li ajaxGet('cooked/overview.part.html', statusElem, function(xhr) { 65*2abb3134SXin Li var elem = document.getElementById('overview'); 66*2abb3134SXin Li elem.innerHTML = xhr.responseText; 67*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 68*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 69*2abb3134SXin Li }); 70*2abb3134SXin Li 71*2abb3134SXin Li loadMetricMetadata(statusElem, function(metadata) { 72*2abb3134SXin Li var elem = document.getElementById('metricMetadata').tBodies[0]; 73*2abb3134SXin Li var metrics = metadata.metrics; 74*2abb3134SXin Li 75*2abb3134SXin Li // Sort by the metric name 76*2abb3134SXin Li var metricNames = Object.getOwnPropertyNames(metrics); 77*2abb3134SXin Li metricNames.sort(); 78*2abb3134SXin Li 79*2abb3134SXin Li var tableHtml = ''; 80*2abb3134SXin Li for (var i = 0; i < metricNames.length; ++i) { 81*2abb3134SXin Li var name = metricNames[i]; 82*2abb3134SXin Li var meta = metrics[name]; 83*2abb3134SXin Li tableHtml += '<tr>'; 84*2abb3134SXin Li tableHtml += '<td>' + name + '</td>'; 85*2abb3134SXin Li tableHtml += '<td>' + meta.owners + '</td>'; 86*2abb3134SXin Li tableHtml += '<td>' + meta.summary + '</td>'; 87*2abb3134SXin Li tableHtml += '</tr>'; 88*2abb3134SXin Li } 89*2abb3134SXin Li elem.innerHTML += tableHtml; 90*2abb3134SXin Li }); 91*2abb3134SXin Li} 92*2abb3134SXin Li 93*2abb3134SXin Li// for metric.html. 94*2abb3134SXin Lifunction initMetric(urlHash, tableStates, statusElem, globals) { 95*2abb3134SXin Li 96*2abb3134SXin Li var metricName = urlHash.get('metric'); 97*2abb3134SXin Li if (metricName === undefined) { 98*2abb3134SXin Li appendMessage(statusElem, "Missing metric name in URL hash."); 99*2abb3134SXin Li return; 100*2abb3134SXin Li } 101*2abb3134SXin Li 102*2abb3134SXin Li loadMetricMetadata(statusElem, function(metadata) { 103*2abb3134SXin Li var meta = metadata.metrics[metricName]; 104*2abb3134SXin Li if (!meta) { 105*2abb3134SXin Li appendMessage(statusElem, 'Found no metadata for ' + metricName); 106*2abb3134SXin Li return; 107*2abb3134SXin Li } 108*2abb3134SXin Li var descElem = document.getElementById('metricDesc'); 109*2abb3134SXin Li descElem.innerHTML = meta.summary; 110*2abb3134SXin Li 111*2abb3134SXin Li // TODO: put owners at the bottom of the page somewhere? 112*2abb3134SXin Li }); 113*2abb3134SXin Li 114*2abb3134SXin Li // Add title and page element 115*2abb3134SXin Li document.title = metricName; 116*2abb3134SXin Li var nameElem = document.getElementById('metricName'); 117*2abb3134SXin Li nameElem.innerHTML = metricName; 118*2abb3134SXin Li 119*2abb3134SXin Li // Add correct links. 120*2abb3134SXin Li var u = document.getElementById('underlying-status'); 121*2abb3134SXin Li u.href = 'cooked/' + metricName + '/status.csv'; 122*2abb3134SXin Li 123*2abb3134SXin Li var distUrl = 'cooked/' + metricName + '/dist.csv'; 124*2abb3134SXin Li var u2 = document.getElementById('underlying-dist'); 125*2abb3134SXin Li u2.href = distUrl; 126*2abb3134SXin Li 127*2abb3134SXin Li ajaxGet(distUrl, statusElem, function(xhr) { 128*2abb3134SXin Li var csvData = xhr.responseText; 129*2abb3134SXin Li var elem = document.getElementById('proportionsDy'); 130*2abb3134SXin Li // Mutate global so we can respond to onclick. 131*2abb3134SXin Li globals.proportionsDygraph = new Dygraph(elem, csvData, {customBars: true}); 132*2abb3134SXin Li }); 133*2abb3134SXin Li 134*2abb3134SXin Li var numReportsUrl = 'cooked/' + metricName + '/num_reports.csv'; 135*2abb3134SXin Li ajaxGet(numReportsUrl, statusElem, function(xhr) { 136*2abb3134SXin Li var csvData = xhr.responseText; 137*2abb3134SXin Li var elem = document.getElementById('num-reports-dy'); 138*2abb3134SXin Li var g = new Dygraph(elem, csvData); 139*2abb3134SXin Li }); 140*2abb3134SXin Li 141*2abb3134SXin Li var massUrl = 'cooked/' + metricName + '/mass.csv'; 142*2abb3134SXin Li ajaxGet(massUrl, statusElem, function(xhr) { 143*2abb3134SXin Li var csvData = xhr.responseText; 144*2abb3134SXin Li var elem = document.getElementById('mass-dy'); 145*2abb3134SXin Li var g = new Dygraph(elem, csvData); 146*2abb3134SXin Li }); 147*2abb3134SXin Li 148*2abb3134SXin Li var tableUrl = 'cooked/' + metricName + '/status.part.html'; 149*2abb3134SXin Li ajaxGet(tableUrl, statusElem, function(xhr) { 150*2abb3134SXin Li var htmlData = xhr.responseText; 151*2abb3134SXin Li var elem = document.getElementById('status_table'); 152*2abb3134SXin Li elem.innerHTML = htmlData; 153*2abb3134SXin Li 154*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 155*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 156*2abb3134SXin Li }); 157*2abb3134SXin Li} 158*2abb3134SXin Li 159*2abb3134SXin Li// NOTE: This was for optional Dygraphs error bars, but it's not hooked up yet. 160*2abb3134SXin Lifunction onMetricCheckboxClick(checkboxElem, proportionsDygraph) { 161*2abb3134SXin Li var checked = checkboxElem.checked; 162*2abb3134SXin Li if (proportionsDygraph === null) { 163*2abb3134SXin Li console.log('NULL'); 164*2abb3134SXin Li } 165*2abb3134SXin Li proportionsDygraph.updateOptions({customBars: checked}); 166*2abb3134SXin Li console.log('HANDLED'); 167*2abb3134SXin Li} 168*2abb3134SXin Li 169*2abb3134SXin Li// for day.html. 170*2abb3134SXin Lifunction initDay(urlHash, tableStates, statusElem) { 171*2abb3134SXin Li var jobId = urlHash.get('jobId'); 172*2abb3134SXin Li var metricName = urlHash.get('metric'); 173*2abb3134SXin Li var date = urlHash.get('date'); 174*2abb3134SXin Li 175*2abb3134SXin Li var err = ''; 176*2abb3134SXin Li if (!jobId) { 177*2abb3134SXin Li err = 'jobId missing from hash'; 178*2abb3134SXin Li } 179*2abb3134SXin Li if (!metricName) { 180*2abb3134SXin Li err = 'metric missing from hash'; 181*2abb3134SXin Li } 182*2abb3134SXin Li if (!date) { 183*2abb3134SXin Li err = 'date missing from hash'; 184*2abb3134SXin Li } 185*2abb3134SXin Li if (err) { 186*2abb3134SXin Li appendMessage(statusElem, err); 187*2abb3134SXin Li } 188*2abb3134SXin Li 189*2abb3134SXin Li // Add title and page element 190*2abb3134SXin Li var titleStr = metricName + ' on ' + date; 191*2abb3134SXin Li document.title = titleStr; 192*2abb3134SXin Li var mElem = document.getElementById('metricDay'); 193*2abb3134SXin Li mElem.innerHTML = titleStr; 194*2abb3134SXin Li 195*2abb3134SXin Li // Add correct links. 196*2abb3134SXin Li var u = document.getElementById('underlying'); 197*2abb3134SXin Li u.href = '../' + jobId + '/raw/' + metricName + '/' + date + 198*2abb3134SXin Li '/results.csv'; 199*2abb3134SXin Li 200*2abb3134SXin Li // Add correct links. 201*2abb3134SXin Li var u_res = document.getElementById('residual'); 202*2abb3134SXin Li u_res.src = '../' + jobId + '/raw/' + metricName + '/' + date + 203*2abb3134SXin Li '/residual.png'; 204*2abb3134SXin Li 205*2abb3134SXin Li var url = '../' + jobId + '/cooked/' + metricName + '/' + date + '.part.html'; 206*2abb3134SXin Li ajaxGet(url, statusElem, function(xhr) { 207*2abb3134SXin Li var htmlData = xhr.responseText; 208*2abb3134SXin Li var elem = document.getElementById('results_table'); 209*2abb3134SXin Li elem.innerHTML = htmlData; 210*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 211*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 212*2abb3134SXin Li }); 213*2abb3134SXin Li} 214*2abb3134SXin Li 215*2abb3134SXin Li// for assoc-overview.html. 216*2abb3134SXin Lifunction initAssocOverview(urlHash, tableStates, statusElem) { 217*2abb3134SXin Li ajaxGet('cooked/assoc-overview.part.html', statusElem, function(xhr) { 218*2abb3134SXin Li var elem = document.getElementById('overview'); 219*2abb3134SXin Li elem.innerHTML = xhr.responseText; 220*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 221*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 222*2abb3134SXin Li }); 223*2abb3134SXin Li} 224*2abb3134SXin Li 225*2abb3134SXin Li// for assoc-metric.html. 226*2abb3134SXin Lifunction initAssocMetric(urlHash, tableStates, statusElem) { 227*2abb3134SXin Li var metricName = urlHash.get('metric'); 228*2abb3134SXin Li if (metricName === undefined) { 229*2abb3134SXin Li appendMessage(statusElem, "Missing metric name in URL hash."); 230*2abb3134SXin Li return; 231*2abb3134SXin Li } 232*2abb3134SXin Li 233*2abb3134SXin Li // Add title and page element 234*2abb3134SXin Li var title = metricName + ': pairs of variables'; 235*2abb3134SXin Li document.title = title; 236*2abb3134SXin Li var pageTitleElem = document.getElementById('pageTitle'); 237*2abb3134SXin Li pageTitleElem.innerHTML = title; 238*2abb3134SXin Li 239*2abb3134SXin Li // Add correct links. 240*2abb3134SXin Li var u = document.getElementById('underlying-status'); 241*2abb3134SXin Li u.href = 'cooked/' + metricName + '/metric-status.csv'; 242*2abb3134SXin Li 243*2abb3134SXin Li var csvPath = 'cooked/' + metricName + '/metric-status.part.html'; 244*2abb3134SXin Li ajaxGet(csvPath, statusElem, function(xhr) { 245*2abb3134SXin Li var elem = document.getElementById('metric_table'); 246*2abb3134SXin Li elem.innerHTML = xhr.responseText; 247*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 248*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 249*2abb3134SXin Li }); 250*2abb3134SXin Li} 251*2abb3134SXin Li 252*2abb3134SXin Li// Function to help us find the *.part.html files. 253*2abb3134SXin Li// 254*2abb3134SXin Li// NOTE: This naming convention matches the one defined in task_spec.py 255*2abb3134SXin Li// AssocTaskSpec. 256*2abb3134SXin Lifunction formatAssocRelPath(metricName, var1, var2) { 257*2abb3134SXin Li var varDir = var1 + '_X_' + var2.replace('..', '_'); 258*2abb3134SXin Li return metricName + '/' + varDir; 259*2abb3134SXin Li} 260*2abb3134SXin Li 261*2abb3134SXin Li// for assoc-pair.html 262*2abb3134SXin Lifunction initAssocPair(urlHash, tableStates, statusElem, globals) { 263*2abb3134SXin Li 264*2abb3134SXin Li var metricName = urlHash.get('metric'); 265*2abb3134SXin Li if (metricName === undefined) { 266*2abb3134SXin Li appendMessage(statusElem, "Missing metric name in URL hash."); 267*2abb3134SXin Li return; 268*2abb3134SXin Li } 269*2abb3134SXin Li var var1 = urlHash.get('var1'); 270*2abb3134SXin Li if (var1 === undefined) { 271*2abb3134SXin Li appendMessage(statusElem, "Missing var1 in URL hash."); 272*2abb3134SXin Li return; 273*2abb3134SXin Li } 274*2abb3134SXin Li var var2 = urlHash.get('var2'); 275*2abb3134SXin Li if (var2 === undefined) { 276*2abb3134SXin Li appendMessage(statusElem, "Missing var2 in URL hash."); 277*2abb3134SXin Li return; 278*2abb3134SXin Li } 279*2abb3134SXin Li 280*2abb3134SXin Li var relPath = formatAssocRelPath(metricName, var1, var2); 281*2abb3134SXin Li 282*2abb3134SXin Li // Add title and page element 283*2abb3134SXin Li var title = metricName + ': ' + var1 + ' vs. ' + var2; 284*2abb3134SXin Li document.title = title; 285*2abb3134SXin Li var pageTitleElem = document.getElementById('pageTitle'); 286*2abb3134SXin Li pageTitleElem.innerHTML = title; 287*2abb3134SXin Li 288*2abb3134SXin Li // Add correct links. 289*2abb3134SXin Li var u = document.getElementById('underlying-status'); 290*2abb3134SXin Li u.href = 'cooked/' + relPath + '/pair-status.csv'; 291*2abb3134SXin Li 292*2abb3134SXin Li /* 293*2abb3134SXin Li var distUrl = 'cooked/' + metricName + '/dist.csv'; 294*2abb3134SXin Li var u2 = document.getElementById('underlying-dist'); 295*2abb3134SXin Li u2.href = distUrl; 296*2abb3134SXin Li */ 297*2abb3134SXin Li 298*2abb3134SXin Li var tableUrl = 'cooked/' + relPath + '/pair-status.part.html'; 299*2abb3134SXin Li ajaxGet(tableUrl, statusElem, function(xhr) { 300*2abb3134SXin Li var htmlData = xhr.responseText; 301*2abb3134SXin Li var elem = document.getElementById('status_table'); 302*2abb3134SXin Li elem.innerHTML = htmlData; 303*2abb3134SXin Li 304*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 305*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 306*2abb3134SXin Li }); 307*2abb3134SXin Li} 308*2abb3134SXin Li 309*2abb3134SXin Li// for assoc-day.html. 310*2abb3134SXin Lifunction initAssocDay(urlHash, tableStates, statusElem) { 311*2abb3134SXin Li var jobId = urlHash.get('jobId'); 312*2abb3134SXin Li var metricName = urlHash.get('metric'); 313*2abb3134SXin Li var var1 = urlHash.get('var1'); 314*2abb3134SXin Li var var2 = urlHash.get('var2'); 315*2abb3134SXin Li var date = urlHash.get('date'); 316*2abb3134SXin Li 317*2abb3134SXin Li var err = ''; 318*2abb3134SXin Li if (!jobId) { 319*2abb3134SXin Li err = 'jobId missing from hash'; 320*2abb3134SXin Li } 321*2abb3134SXin Li if (!metricName) { 322*2abb3134SXin Li err = 'metric missing from hash'; 323*2abb3134SXin Li } 324*2abb3134SXin Li if (!var1) { 325*2abb3134SXin Li err = 'var1 missing from hash'; 326*2abb3134SXin Li } 327*2abb3134SXin Li if (!var2) { 328*2abb3134SXin Li err = 'var2 missing from hash'; 329*2abb3134SXin Li } 330*2abb3134SXin Li if (!date) { 331*2abb3134SXin Li err = 'date missing from hash'; 332*2abb3134SXin Li } 333*2abb3134SXin Li if (err) { 334*2abb3134SXin Li appendMessage(statusElem, err); 335*2abb3134SXin Li } 336*2abb3134SXin Li 337*2abb3134SXin Li // Add title and page element 338*2abb3134SXin Li var titleStr = metricName + ': ' + var1 + ' vs. ' + var2 + ' on ' + date; 339*2abb3134SXin Li document.title = titleStr; 340*2abb3134SXin Li var mElem = document.getElementById('metricDay'); 341*2abb3134SXin Li mElem.innerHTML = titleStr; 342*2abb3134SXin Li 343*2abb3134SXin Li var relPath = formatAssocRelPath(metricName, var1, var2); 344*2abb3134SXin Li 345*2abb3134SXin Li // Add correct links. 346*2abb3134SXin Li var u = document.getElementById('underlying'); 347*2abb3134SXin Li u.href = '../' + jobId + '/raw/' + relPath + '/' + date + 348*2abb3134SXin Li '/assoc-results.csv'; 349*2abb3134SXin Li 350*2abb3134SXin Li var url = '../' + jobId + '/cooked/' + relPath + '/' + date + '.part.html'; 351*2abb3134SXin Li ajaxGet(url, statusElem, function(xhr) { 352*2abb3134SXin Li var htmlData = xhr.responseText; 353*2abb3134SXin Li var elem = document.getElementById('results_table'); 354*2abb3134SXin Li elem.innerHTML = htmlData; 355*2abb3134SXin Li makeTablesSortable(urlHash, [elem], tableStates); 356*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 357*2abb3134SXin Li }); 358*2abb3134SXin Li} 359*2abb3134SXin Li 360*2abb3134SXin Li// This is the onhashchange handler of *all* HTML files. 361*2abb3134SXin Lifunction onHashChange(urlHash, tableStates, statusElem) { 362*2abb3134SXin Li updateTables(urlHash, tableStates, statusElem); 363*2abb3134SXin Li} 364