1<%--
2  ~ Copyright (c) 2016 Google Inc. All Rights Reserved.
3  ~
4  ~ Licensed under the Apache License, Version 2.0 (the "License"); you
5  ~ may not use this file except in compliance with the License. You may
6  ~ obtain a copy of the License at
7  ~
8  ~     http://www.apache.org/licenses/LICENSE-2.0
9  ~
10  ~ Unless required by applicable law or agreed to in writing, software
11  ~ distributed under the License is distributed on an "AS IS" BASIS,
12  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13  ~ implied. See the License for the specific language governing
14  ~ permissions and limitations under the License.
15  --%>
16<%@ page contentType='text/html;charset=UTF-8' language='java' %>
17<%@ taglib prefix='fn' uri='http://java.sun.com/jsp/jstl/functions' %>
18<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%>
19
20<html>
21  <%@ include file="header.jsp" %>
22  <link type='text/css' href='/css/show_table.css' rel='stylesheet'>
23  <link type='text/css' href='/css/show_test_runs_common.css' rel='stylesheet'>
24  <link type='text/css' href='/css/search_header.css' rel='stylesheet'>
25  <script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>
26  <script src='https://www.gstatic.com/external_hosted/moment/min/moment-with-locales.min.js'></script>
27  <script src='js/search_header.js'></script>
28  <script src='js/time.js'></script>
29  <script type='text/javascript'>
30      google.charts.load('current', {'packages':['table', 'corechart']});
31      google.charts.setOnLoadCallback(drawGridTable);
32      google.charts.setOnLoadCallback(activateLogLinks);
33      google.charts.setOnLoadCallback(drawPieChart);
34      google.charts.setOnLoadCallback(function() {
35          $('.gradient').removeClass('gradient');
36      });
37
38      var search;
39
40      $(document).ready(function() {
41          search = $('#filter-bar').createSearchHeader('Module: ', '${testName}', refresh);
42          search.addFilter('Branch', 'branch', {
43            corpus: ${branches}
44          }, ${branch});
45          search.addFilter('Device', 'device', {
46            corpus: ${devices}
47          }, ${device});
48          search.addFilter('Device Build ID', 'deviceBuildId', {}, ${deviceBuildId});
49          search.addFilter('Test Build ID', 'testBuildId', {}, ${testBuildId});
50          search.addFilter('Host', 'hostname', {}, ${hostname});
51          search.addFilter('Passing Count', 'passing', {
52            validate: 'inequality',
53            width: 's2'
54          }, ${passing});
55          search.addFilter('Non-Passing Count', 'nonpassing', {
56            validate: 'inequality',
57            width: 's2'
58          }, ${nonpassing});
59          search.addRunTypeCheckboxes(${showPresubmit}, ${showPostsubmit});
60          search.display();
61
62          // disable buttons on load
63          if (!${hasNewer}) {
64              $('#newer-button').toggleClass('disabled');
65          }
66          if (!${hasOlder}) {
67              $('#older-button').toggleClass('disabled');
68          }
69          $('#treeLink').click(function() {
70              window.open('/show_tree?testName=${testName}&treeDefault=true', '_self');
71          });
72          $('#newer-button').click(prev);
73          $('#older-button').click(next);
74      });
75
76      // Actives the log links to display the log info modal when clicked.
77      function activateLogLinks() {
78          $('.info-btn').click(function(e) {
79              showLog(${logInfoMap}[$(this).data('col')]);
80          });
81      }
82
83      /** Displays a modal window with the specified log entries.
84       *
85       * @param logEntries Array of string arrays. Each entry in the outer array
86       *                   must contain (1) name string, and (2) url string.
87       */
88      function showLog(logEntries) {
89          if (!logEntries || logEntries.length == 0) return;
90
91          var logList = $('<ul class="collection"></ul>');
92          var entries = logEntries.reduce(function(acc, entry) {
93              if (!entry || entry.length == 0) return acc;
94              var link = '<a href="' + entry[1] + '"';
95              link += 'class="collection-item">' + entry[0] + '</li>';
96              return acc + link;
97          }, '');
98          logList.html(entries);
99          var infoContainer = $('#info-modal>.modal-content>.info-container');
100          infoContainer.empty();
101          logList.appendTo(infoContainer);
102          var modal = $('#info-modal');
103          modal.modal();
104          modal.modal('open');
105      }
106
107      // refresh the page to see the selected test types (pre-/post-submit)
108      function refresh() {
109          if($(this).hasClass('disabled')) return;
110          var link = '${pageContext.request.contextPath}' +
111              '/show_table?testName=${testName}' + search.args();
112          if (${unfiltered}) {
113              link += '&unfiltered=';
114          }
115          window.open(link,'_self');
116      }
117
118      // view older data
119      function next() {
120          if($(this).hasClass('disabled')) return;
121          var endTime = ${startTime};
122          var link = '${pageContext.request.contextPath}' +
123              '/show_table?testName=${testName}&endTime=' + endTime +
124              search.args();
125          if (${unfiltered}) {
126              link += '&unfiltered=';
127          }
128          window.open(link,'_self');
129      }
130
131      // view newer data
132      function prev() {
133          if($(this).hasClass('disabled')) return;
134          var startTime = ${endTime};
135          var link = '${pageContext.request.contextPath}' +
136              '/show_table?testName=${testName}&startTime=' + startTime +
137              search.args();
138          if (${unfiltered}) {
139              link += '&unfiltered=';
140          }
141          window.open(link,'_self');
142        }
143
144      // to draw pie chart
145      function drawPieChart() {
146          var topBuildResultCounts = ${topBuildResultCounts};
147          if (topBuildResultCounts.length < 1) {
148              return;
149          }
150          var resultNames = ${resultNamesJson};
151          var rows = resultNames.map(function(res, i) {
152              nickname = res.replace('TEST_CASE_RESULT_', '').replace('_', ' ')
153                         .trim().toLowerCase();
154              return [nickname, parseInt(topBuildResultCounts[i])];
155          });
156          rows.unshift(['Result', 'Count']);
157
158          // Get CSS color definitions (or default to white)
159          var colors = resultNames.map(function(res) {
160              return $('.' + res).css('background-color') || 'white';
161          });
162
163          var data = google.visualization.arrayToDataTable(rows);
164          var options = {
165              is3D: false,
166              colors: colors,
167              fontName: 'Roboto',
168              fontSize: '14px',
169              legend: {position: 'bottom'},
170              tooltip: {showColorCode: true, ignoreBounds: false},
171              chartArea: {height: '80%', width: '90%'},
172              pieHole: 0.4
173          };
174
175          var chart = new google.visualization.PieChart(document.getElementById('pie-chart-div'));
176          chart.draw(data, options);
177      }
178
179      // table for grid data
180      function drawGridTable() {
181          var data = new google.visualization.DataTable();
182
183          // Add column headers.
184          headerRow = ${headerRow};
185          headerRow.forEach(function(d, i) {
186              var classNames = 'table-header-content';
187              if (i == 0) classNames += ' table-header-legend';
188              data.addColumn('string', '<span class="' + classNames + '">' +
189                             d + '</span>');
190          });
191
192          var timeGrid = ${timeGrid};
193          var durationGrid = ${durationGrid};
194          var summaryGrid = ${summaryGrid};
195          var resultsGrid = ${resultsGrid};
196
197          // Format time grid to a formatted date
198          timeGrid = timeGrid.map(function(row) {
199              return row.map(function(cell, j) {
200                  if (j == 0) return cell;
201                  return moment().renderTime(cell);
202              });
203          });
204
205          // Format duration grid to HH:mm:ss.SSS
206          durationGrid = durationGrid.map(function(row) {
207              return row.map(function(cell, j) {
208                  if (j == 0) return cell;
209                  return moment().renderDuration(cell);
210              });
211          });
212
213          // add rows to the data.
214          data.addRows(timeGrid);
215          data.addRows(durationGrid);
216          data.addRows(summaryGrid);
217          data.addRows(resultsGrid);
218
219          var table = new google.visualization.Table(document.getElementById('grid-table-div'));
220          var classNames = {
221              headerRow : 'table-header',
222              headerCell : 'table-header-cell'
223          };
224          var options = {
225              showRowNumber: false,
226              alternatingRowStyle: true,
227              allowHtml: true,
228              frozenColumns: 1,
229              cssClassNames: classNames,
230              sort: 'disable'
231          };
232          table.draw(data, options);
233      }
234  </script>
235
236  <body>
237    <div class='wide container'>
238      <div class='row'>
239        <div class='col s12'>
240          <div class='card'>
241            <ul class='tabs'>
242              <li class='tab col s6' id='treeLink'><a>Tree</a></li>
243              <li class='tab col s6'><a class='active'>Table</a></li>
244            </ul>
245          </div>
246          <div id='filter-bar'></div>
247        </div>
248        <div class='col s7'>
249          <div class='col s12 card center-align'>
250            <div id='legend-wrapper'>
251              <c:forEach items='${resultNames}' var='res'>
252                <div class='center-align legend-entry'>
253                  <c:set var='trimmed' value='${fn:replace(res, "TEST_CASE_RESULT_", "")}'/>
254                  <c:set var='nickname' value='${fn:replace(trimmed, "_", " ")}'/>
255                  <label for='${res}'>${nickname}</label>
256                  <div id='${res}' class='${res} legend-bubble'></div>
257                </div>
258              </c:forEach>
259            </div>
260          </div>
261          <div id='profiling-container' class='col s12'>
262            <c:choose>
263              <c:when test='${empty profilingPointNames}'>
264                <div id='error-div' class='center-align card'><h5>${error}</h5></div>
265              </c:when>
266              <c:otherwise>
267                <ul id='profiling-body' class='collapsible' data-collapsible='accordion'>
268                  <li>
269                    <div class='collapsible-header'><i class='material-icons'>timeline</i>Profiling Graphs</div>
270                    <div class='collapsible-body'>
271                      <ul id='profiling-list' class='collection'>
272                        <c:forEach items='${profilingPointNames}' var='pt'>
273                          <c:set var='profPointArgs' value='testName=${testName}&profilingPoint=${pt}'/>
274                          <c:set var='timeArgs' value='endTime=${endTime}'/>
275                          <a href='/show_graph?${profPointArgs}&${timeArgs}'
276                             class='collection-item profiling-point-name'>${pt}
277                          </a>
278                        </c:forEach>
279                      </ul>
280                    </div>
281                  </li>
282                  <li>
283                    <a class='collapsible-link' href='/show_performance_digest?testName=${testName}'>
284                      <div class='collapsible-header'><i class='material-icons'>toc</i>Performance Digest</div>
285                    </a>
286                  </li>
287                </ul>
288              </c:otherwise>
289            </c:choose>
290          </div>
291        </div>
292        <div class='col s5 valign-wrapper'>
293          <!-- pie chart -->
294          <div id='pie-chart-wrapper' class='col s12 valign center-align card'>
295            <h6 class='pie-chart-title'>Test Status for Device Build ID: ${topBuildId}</h6>
296            <div id='pie-chart-div'></div>
297          </div>
298        </div>
299      </div>
300
301      <div class='col s12'>
302        <div id='chart-holder' class='col s12 card'>
303          <!-- Grid tables-->
304          <div id='grid-table-div'></div>
305        </div>
306      </div>
307      <div id='newer-wrapper' class='page-button-wrapper fixed-action-btn'>
308        <a id='newer-button' class='btn-floating btn red waves-effect'>
309          <i class='large material-icons'>keyboard_arrow_left</i>
310        </a>
311      </div>
312      <div id='older-wrapper' class='page-button-wrapper fixed-action-btn'>
313        <a id='older-button' class='btn-floating btn red waves-effect'>
314          <i class='large material-icons'>keyboard_arrow_right</i>
315        </a>
316      </div>
317    </div>
318    <div id="help-modal" class="modal">
319      <div class="modal-content">
320        <h4>${searchHelpHeader}</h4>
321        <p>${searchHelpBody}</p>
322      </div>
323      <div class="modal-footer">
324        <a href="#!" class="modal-action modal-close waves-effect btn-flat">Close</a>
325      </div>
326    </div>
327    <div id="info-modal" class="modal">
328      <div class="modal-content">
329        <h4>Logs</h4>
330        <div class="info-container"></div>
331      </div>
332      <div class="modal-footer">
333        <a href="#!" class="modal-action modal-close waves-effect btn-flat">Close</a>
334      </div>
335    </div>
336    <%@ include file="footer.jsp" %>
337  </body>
338</html>
339