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