• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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