diff options
Diffstat (limited to 'ui/ui.js')
-rw-r--r-- | ui/ui.js | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/ui/ui.js b/ui/ui.js new file mode 100644 index 0000000..b74a8e2 --- /dev/null +++ b/ui/ui.js @@ -0,0 +1,363 @@ +// Dashboard UI functions. +// +// This is shared between all HTML pages. + +'use strict'; + +// Append a message to an element. Used for errors. +function appendMessage(elem, msg) { + elem.innerHTML += msg + '<br />'; +} + +// jQuery-like AJAX helper, but simpler. + +// Requires an element with id "status" to show errors. +// +// Args: +// errElem: optional element to append error messages to. If null, then +// alert() on error. +// success: callback that is passed the xhr object. +function ajaxGet(url, errElem, success) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true /*async*/); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4 /*DONE*/) { + return; + } + + if (xhr.status != 200) { + var msg = 'ERROR requesting ' + url + ': ' + xhr.status + ' ' + + xhr.statusText; + if (errElem) { + appendMessage(errElem, msg); + } else { + alert(msg); + } + return; + } + + success(xhr); + }; + xhr.send(); +} + +// Load metadata about the metrics. +// metric-metadata.json is just 14 KB, so we load it for every page. +// +// callback: +// on metric page, just pick out the right description. +// on overview page, populate them ALL with tool tips? +// Or create another column? +function loadMetricMetadata(errElem, success) { + // TODO: Should we make metric-metadata.json optional? Some may not have it. + + ajaxGet('metric-metadata.json', errElem, function(xhr) { + // TODO: handle parse error + var m = JSON.parse(xhr.responseText); + success(m); + }); +} + +// for overview.html. +function initOverview(urlHash, tableStates, statusElem) { + + ajaxGet('cooked/overview.part.html', statusElem, function(xhr) { + var elem = document.getElementById('overview'); + elem.innerHTML = xhr.responseText; + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); + + loadMetricMetadata(statusElem, function(metadata) { + var elem = document.getElementById('metricMetadata').tBodies[0]; + var metrics = metadata.metrics; + + // Sort by the metric name + var metricNames = Object.getOwnPropertyNames(metrics); + metricNames.sort(); + + var tableHtml = ''; + for (var i = 0; i < metricNames.length; ++i) { + var name = metricNames[i]; + var meta = metrics[name]; + tableHtml += '<tr>'; + tableHtml += '<td>' + name + '</td>'; + tableHtml += '<td>' + meta.owners + '</td>'; + tableHtml += '<td>' + meta.summary + '</td>'; + tableHtml += '</tr>'; + } + elem.innerHTML += tableHtml; + }); +} + +// for metric.html. +function initMetric(urlHash, tableStates, statusElem, globals) { + + var metricName = urlHash.get('metric'); + if (metricName === undefined) { + appendMessage(statusElem, "Missing metric name in URL hash."); + return; + } + + loadMetricMetadata(statusElem, function(metadata) { + var meta = metadata.metrics[metricName]; + if (!meta) { + appendMessage(statusElem, 'Found no metadata for ' + metricName); + return; + } + var descElem = document.getElementById('metricDesc'); + descElem.innerHTML = meta.summary; + + // TODO: put owners at the bottom of the page somewhere? + }); + + // Add title and page element + document.title = metricName; + var nameElem = document.getElementById('metricName'); + nameElem.innerHTML = metricName; + + // Add correct links. + var u = document.getElementById('underlying-status'); + u.href = 'cooked/' + metricName + '/status.csv'; + + var distUrl = 'cooked/' + metricName + '/dist.csv'; + var u2 = document.getElementById('underlying-dist'); + u2.href = distUrl; + + ajaxGet(distUrl, statusElem, function(xhr) { + var csvData = xhr.responseText; + var elem = document.getElementById('proportionsDy'); + // Mutate global so we can respond to onclick. + globals.proportionsDygraph = new Dygraph(elem, csvData, {customBars: true}); + }); + + var numReportsUrl = 'cooked/' + metricName + '/num_reports.csv'; + ajaxGet(numReportsUrl, statusElem, function(xhr) { + var csvData = xhr.responseText; + var elem = document.getElementById('num-reports-dy'); + var g = new Dygraph(elem, csvData); + }); + + var massUrl = 'cooked/' + metricName + '/mass.csv'; + ajaxGet(massUrl, statusElem, function(xhr) { + var csvData = xhr.responseText; + var elem = document.getElementById('mass-dy'); + var g = new Dygraph(elem, csvData); + }); + + var tableUrl = 'cooked/' + metricName + '/status.part.html'; + ajaxGet(tableUrl, statusElem, function(xhr) { + var htmlData = xhr.responseText; + var elem = document.getElementById('status_table'); + elem.innerHTML = htmlData; + + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// NOTE: This was for optional Dygraphs error bars, but it's not hooked up yet. +function onMetricCheckboxClick(checkboxElem, proportionsDygraph) { + var checked = checkboxElem.checked; + if (proportionsDygraph === null) { + console.log('NULL'); + } + proportionsDygraph.updateOptions({customBars: checked}); + console.log('HANDLED'); +} + +// for day.html. +function initDay(urlHash, tableStates, statusElem) { + var jobId = urlHash.get('jobId'); + var metricName = urlHash.get('metric'); + var date = urlHash.get('date'); + + var err = ''; + if (!jobId) { + err = 'jobId missing from hash'; + } + if (!metricName) { + err = 'metric missing from hash'; + } + if (!date) { + err = 'date missing from hash'; + } + if (err) { + appendMessage(statusElem, err); + } + + // Add title and page element + var titleStr = metricName + ' on ' + date; + document.title = titleStr; + var mElem = document.getElementById('metricDay'); + mElem.innerHTML = titleStr; + + // Add correct links. + var u = document.getElementById('underlying'); + u.href = '../' + jobId + '/raw/' + metricName + '/' + date + + '/results.csv'; + + // Add correct links. + var u_res = document.getElementById('residual'); + u_res.src = '../' + jobId + '/raw/' + metricName + '/' + date + + '/residual.png'; + + var url = '../' + jobId + '/cooked/' + metricName + '/' + date + '.part.html'; + ajaxGet(url, statusElem, function(xhr) { + var htmlData = xhr.responseText; + var elem = document.getElementById('results_table'); + elem.innerHTML = htmlData; + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// for assoc-overview.html. +function initAssocOverview(urlHash, tableStates, statusElem) { + ajaxGet('cooked/assoc-overview.part.html', statusElem, function(xhr) { + var elem = document.getElementById('overview'); + elem.innerHTML = xhr.responseText; + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// for assoc-metric.html. +function initAssocMetric(urlHash, tableStates, statusElem) { + var metricName = urlHash.get('metric'); + if (metricName === undefined) { + appendMessage(statusElem, "Missing metric name in URL hash."); + return; + } + + // Add title and page element + var title = metricName + ': pairs of variables'; + document.title = title; + var pageTitleElem = document.getElementById('pageTitle'); + pageTitleElem.innerHTML = title; + + // Add correct links. + var u = document.getElementById('underlying-status'); + u.href = 'cooked/' + metricName + '/metric-status.csv'; + + var csvPath = 'cooked/' + metricName + '/metric-status.part.html'; + ajaxGet(csvPath, statusElem, function(xhr) { + var elem = document.getElementById('metric_table'); + elem.innerHTML = xhr.responseText; + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// Function to help us find the *.part.html files. +// +// NOTE: This naming convention matches the one defined in task_spec.py +// AssocTaskSpec. +function formatAssocRelPath(metricName, var1, var2) { + var varDir = var1 + '_X_' + var2.replace('..', '_'); + return metricName + '/' + varDir; +} + +// for assoc-pair.html +function initAssocPair(urlHash, tableStates, statusElem, globals) { + + var metricName = urlHash.get('metric'); + if (metricName === undefined) { + appendMessage(statusElem, "Missing metric name in URL hash."); + return; + } + var var1 = urlHash.get('var1'); + if (var1 === undefined) { + appendMessage(statusElem, "Missing var1 in URL hash."); + return; + } + var var2 = urlHash.get('var2'); + if (var2 === undefined) { + appendMessage(statusElem, "Missing var2 in URL hash."); + return; + } + + var relPath = formatAssocRelPath(metricName, var1, var2); + + // Add title and page element + var title = metricName + ': ' + var1 + ' vs. ' + var2; + document.title = title; + var pageTitleElem = document.getElementById('pageTitle'); + pageTitleElem.innerHTML = title; + + // Add correct links. + var u = document.getElementById('underlying-status'); + u.href = 'cooked/' + relPath + '/pair-status.csv'; + + /* + var distUrl = 'cooked/' + metricName + '/dist.csv'; + var u2 = document.getElementById('underlying-dist'); + u2.href = distUrl; + */ + + var tableUrl = 'cooked/' + relPath + '/pair-status.part.html'; + ajaxGet(tableUrl, statusElem, function(xhr) { + var htmlData = xhr.responseText; + var elem = document.getElementById('status_table'); + elem.innerHTML = htmlData; + + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// for assoc-day.html. +function initAssocDay(urlHash, tableStates, statusElem) { + var jobId = urlHash.get('jobId'); + var metricName = urlHash.get('metric'); + var var1 = urlHash.get('var1'); + var var2 = urlHash.get('var2'); + var date = urlHash.get('date'); + + var err = ''; + if (!jobId) { + err = 'jobId missing from hash'; + } + if (!metricName) { + err = 'metric missing from hash'; + } + if (!var1) { + err = 'var1 missing from hash'; + } + if (!var2) { + err = 'var2 missing from hash'; + } + if (!date) { + err = 'date missing from hash'; + } + if (err) { + appendMessage(statusElem, err); + } + + // Add title and page element + var titleStr = metricName + ': ' + var1 + ' vs. ' + var2 + ' on ' + date; + document.title = titleStr; + var mElem = document.getElementById('metricDay'); + mElem.innerHTML = titleStr; + + var relPath = formatAssocRelPath(metricName, var1, var2); + + // Add correct links. + var u = document.getElementById('underlying'); + u.href = '../' + jobId + '/raw/' + relPath + '/' + date + + '/assoc-results.csv'; + + var url = '../' + jobId + '/cooked/' + relPath + '/' + date + '.part.html'; + ajaxGet(url, statusElem, function(xhr) { + var htmlData = xhr.responseText; + var elem = document.getElementById('results_table'); + elem.innerHTML = htmlData; + makeTablesSortable(urlHash, [elem], tableStates); + updateTables(urlHash, tableStates, statusElem); + }); +} + +// This is the onhashchange handler of *all* HTML files. +function onHashChange(urlHash, tableStates, statusElem) { + updateTables(urlHash, tableStates, statusElem); +} |