xref: /aosp_15_r20/external/rappor/ui/ui.js (revision 2abb31345f6c95944768b5222a9a5ed3fc68cc00)
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