summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYoung Gyu Park <younggyu@google.com>2018-06-26 11:36:17 +0900
committerYoung Gyu Park <younggyu@google.com>2018-07-11 10:45:31 +0900
commitc0204c1c2c600bb21515207356d6f5a2626153d7 (patch)
tree2fde7ed4011683dba452ad1f05f06c8cbaf841c5 /src
parentf967235ef688948f3aef233424966f6b92558772 (diff)
downloaddashboard-c0204c1c2c600bb21515207356d6f5a2626153d7.tar.gz
Coverage Line Graph on coverage page.android-p-preview-5
Test: go/vts-web-staging Bug: 110806899 Change-Id: Ieade25661ac681550ca7be2ce00c3ad89a605f6c
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/android/vts/servlet/ShowCoverageOverviewServlet.java214
-rw-r--r--src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp164
-rw-r--r--src/main/webapp/css/test_results.css8
-rw-r--r--src/main/webapp/js/test_results.js13
4 files changed, 391 insertions, 8 deletions
diff --git a/src/main/java/com/android/vts/servlet/ShowCoverageOverviewServlet.java b/src/main/java/com/android/vts/servlet/ShowCoverageOverviewServlet.java
index 2d09c0b..f0cafbe 100644
--- a/src/main/java/com/android/vts/servlet/ShowCoverageOverviewServlet.java
+++ b/src/main/java/com/android/vts/servlet/ShowCoverageOverviewServlet.java
@@ -19,6 +19,7 @@ package com.android.vts.servlet;
import com.android.vts.entity.TestCoverageStatusEntity;
import com.android.vts.entity.TestEntity;
import com.android.vts.entity.TestRunEntity;
+import com.android.vts.entity.TestRunEntity.TestRunType;
import com.android.vts.proto.VtsReportMessage;
import com.android.vts.util.DatastoreHelper;
import com.android.vts.util.FilterUtil;
@@ -27,27 +28,59 @@ import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
+import com.google.cloud.datastore.Datastore;
+import com.google.cloud.datastore.DatastoreOptions;
+import com.google.cloud.datastore.LongValue;
+import com.google.cloud.datastore.PathElement;
+import com.google.cloud.datastore.QueryResults;
+import com.google.cloud.datastore.StringValue;
+import com.google.cloud.datastore.StructuredQuery.CompositeFilter;
+import com.google.cloud.datastore.StructuredQuery.Filter;
+import com.google.cloud.datastore.StructuredQuery.PropertyFilter;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
+import com.google.visualization.datasource.DataSourceHelper;
+import com.google.visualization.datasource.DataSourceRequest;
+import com.google.visualization.datasource.base.DataSourceException;
+import com.google.visualization.datasource.base.ReasonType;
+import com.google.visualization.datasource.base.ResponseStatus;
+import com.google.visualization.datasource.base.StatusType;
+import com.google.visualization.datasource.base.TypeMismatchException;
+import com.google.visualization.datasource.datatable.ColumnDescription;
+import com.google.visualization.datasource.datatable.DataTable;
+import com.google.visualization.datasource.datatable.TableRow;
+import com.google.visualization.datasource.datatable.value.DateTimeValue;
+import com.google.visualization.datasource.datatable.value.DateValue;
+import com.google.visualization.datasource.datatable.value.NumberValue;
+import com.google.visualization.datasource.datatable.value.ValueType;
+import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.util.TimeZone;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
/**
* Represents the servlet that is invoked on loading the coverage overview page.
*/
public class ShowCoverageOverviewServlet extends BaseServlet {
- private static final String COVERAGE_OVERVIEW_JSP = "WEB-INF/jsp/show_coverage_overview.jsp";
-
@Override
public PageType getNavParentType() {
return PageType.COVERAGE_OVERVIEW;
@@ -61,8 +94,68 @@ public class ShowCoverageOverviewServlet extends BaseServlet {
@Override
public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
throws IOException {
- RequestDispatcher dispatcher = null;
+
+ String pageType =
+ request.getParameter("pageType") == null ? "html" : request.getParameter("pageType");
+
+ RequestDispatcher dispatcher;
+ if (pageType.equalsIgnoreCase("html")) {
+ dispatcher = this.getCoverageDispatcher(request, response);
+ try {
+ request.setAttribute("pageType", pageType);
+ response.setStatus(HttpServletResponse.SC_OK);
+ dispatcher.forward(request, response);
+ } catch (ServletException e) {
+ logger.log(Level.SEVERE, "Servlet Exception caught : ", e);
+ }
+ } else {
+
+ String testName = request.getParameter("testName");
+
+ DataTable data = getCoverageDataTable(testName);
+ DataSourceRequest dsRequest = null;
+
+ try {
+ // Extract the datasource request parameters.
+ dsRequest = new DataSourceRequest(request);
+
+ // NOTE: If you want to work in restricted mode, which means that only
+ // requests from the same domain can access the data source, uncomment the following call.
+ //
+ // DataSourceHelper.verifyAccessApproved(dsRequest);
+
+ // Apply the query to the data table.
+ DataTable newData = DataSourceHelper.applyQuery(dsRequest.getQuery(), data,
+ dsRequest.getUserLocale());
+
+ // Set the response.
+ DataSourceHelper.setServletResponse(newData, dsRequest, response);
+ } catch (RuntimeException rte) {
+ logger.log(Level.SEVERE, "A runtime exception has occured", rte);
+ ResponseStatus status = new ResponseStatus(StatusType.ERROR, ReasonType.INTERNAL_ERROR,
+ rte.getMessage());
+ if (dsRequest == null) {
+ dsRequest = DataSourceRequest.getDefaultDataSourceRequest(request);
+ }
+ DataSourceHelper.setServletErrorResponse(status, dsRequest, response);
+ } catch (DataSourceException e) {
+ if (dsRequest != null) {
+ DataSourceHelper.setServletErrorResponse(e, dsRequest, response);
+ } else {
+ DataSourceHelper.setServletErrorResponse(e, request, response);
+ }
+ }
+ }
+ }
+
+ private RequestDispatcher getCoverageDispatcher(
+ HttpServletRequest request, HttpServletResponse response) {
+
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
+
+ String COVERAGE_OVERVIEW_JSP = "WEB-INF/jsp/show_coverage_overview.jsp";
+
+ RequestDispatcher dispatcher = null;
boolean unfiltered = request.getParameter("unfiltered") != null;
boolean showPresubmit = request.getParameter("showPresubmit") != null;
boolean showPostsubmit = request.getParameter("showPostsubmit") != null;
@@ -164,10 +257,117 @@ public class ShowCoverageOverviewServlet extends BaseServlet {
request.setAttribute("branches", new Gson().toJson(DatastoreHelper.getAllBranches()));
request.setAttribute("devices", new Gson().toJson(DatastoreHelper.getAllBuildFlavors()));
dispatcher = request.getRequestDispatcher(COVERAGE_OVERVIEW_JSP);
- try {
- dispatcher.forward(request, response);
- } catch (ServletException e) {
- logger.log(Level.SEVERE, "Servlet Exception caught : ", e);
+ return dispatcher;
+ }
+
+ private DataTable getCoverageDataTable(String testName) {
+
+ Datastore datastore = DatastoreOptions.getDefaultInstance().getService();
+
+ DataTable dataTable = new DataTable();
+ ArrayList<ColumnDescription> cd = new ArrayList<>();
+ ColumnDescription startDate = new ColumnDescription("startDate", ValueType.DATETIME, "Date");
+ startDate.setPattern("yyyy-MM-dd");
+ cd.add(startDate);
+ cd.add(new ColumnDescription("coveredLineCount", ValueType.NUMBER,
+ "Covered Source Code Line Count"));
+ cd.add(
+ new ColumnDescription("totalLineCount", ValueType.NUMBER, "Total Source Code Line Count"));
+ cd.add(new ColumnDescription("percentage", ValueType.NUMBER, "Coverage Ratio (%)"));
+
+ dataTable.addColumns(cd);
+
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DATE, -30);
+ Long startTime = cal.getTime().getTime() * 1000;
+ Long endTime = Calendar.getInstance().getTime().getTime() * 1000;
+
+ com.google.cloud.datastore.Key startKey = datastore.newKeyFactory()
+ .setKind(TestRunEntity.KIND)
+ .addAncestors(PathElement.of(TestEntity.KIND, testName),
+ PathElement.of(TestRunEntity.KIND, startTime))
+ .newKey(startTime);
+
+ com.google.cloud.datastore.Key endKey = datastore.newKeyFactory()
+ .setKind(TestRunEntity.KIND)
+ .addAncestors(PathElement.of(TestEntity.KIND, testName),
+ PathElement.of(TestRunEntity.KIND, endTime))
+ .newKey(endTime);
+
+ Filter testRunFilter = CompositeFilter.and(
+ PropertyFilter.lt("__key__", endKey),
+ PropertyFilter.gt("__key__", startKey),
+ PropertyFilter.eq("hasCoverage", true)
+ );
+
+ com.google.cloud.datastore.Query<com.google.cloud.datastore.Entity> testRunQuery =
+ com.google.cloud.datastore.Query
+ .newEntityQueryBuilder()
+ .setKind(TestRunEntity.KIND)
+ .setFilter(testRunFilter)
+ .build();
+
+ List<TestRunEntity> testRunEntityList = new ArrayList<>();
+ QueryResults<com.google.cloud.datastore.Entity> testRunIterator = datastore.run(testRunQuery);
+ while (testRunIterator.hasNext()) {
+ com.google.cloud.datastore.Entity entity = testRunIterator.next();
+
+ Key parentKey = KeyFactory.createKey(TestEntity.KIND, entity.getString("testName"));
+
+ List<LongValue> testCaseIdList = entity.getList("testCaseIds");
+ List<StringValue> linkList = new ArrayList<>();
+ if (entity.contains("logLinks")) {
+ linkList = entity.getList("logLinks");
+ }
+ TestRunEntity testRunEntity = new TestRunEntity(
+ KeyFactory.createKey(parentKey, TestRunEntity.KIND, entity.getLong("startTimestamp")),
+ TestRunType.fromNumber(Long.valueOf(entity.getLong("type")).intValue()),
+ entity.getLong("startTimestamp"),
+ entity.getLong("endTimestamp"),
+ entity.getString("testBuildId"),
+ entity.getString("hostName"),
+ entity.getLong("passCount"),
+ entity.getLong("failCount"),
+ testCaseIdList.stream().map(value -> value.get()).collect(Collectors.toList()),
+ linkList.stream().map(v -> v.get()).collect(Collectors.toList()),
+ entity.getLong("coveredLineCount"),
+ entity.getLong("totalLineCount")
+ );
+ testRunEntityList.add(testRunEntity);
}
+
+ DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
+ Map<String, List<TestRunEntity>> testRunEntityListMap = testRunEntityList.stream().collect(
+ Collectors.groupingBy(v -> dateTimeFormatter.print(v.getStartTimestamp() / 1000))
+ );
+
+ testRunEntityListMap.forEach((key, entityList) -> {
+ if (dataTable.getRows().size() < 10) {
+ GregorianCalendar gCal = new GregorianCalendar();
+ gCal.setTimeZone(TimeZone.getTimeZone("GMT"));
+ gCal.setTimeInMillis(entityList.get(0).getStartTimestamp() / 1000);
+
+ Long sumCoveredLine = entityList.stream().mapToLong(val -> val.getCoveredLineCount()).sum();
+ Long sumTotalLine = entityList.stream().mapToLong(val -> val.getTotalLineCount()).sum();
+ BigDecimal coveredLineNum = new BigDecimal(sumCoveredLine);
+ BigDecimal totalLineNum = new BigDecimal(sumTotalLine);
+ BigDecimal totalPercent = new BigDecimal(100);
+ float percentage = coveredLineNum.multiply(totalPercent).divide(totalLineNum, 2,
+ RoundingMode.HALF_DOWN).floatValue();
+
+ TableRow tableRow = new TableRow();
+ tableRow.addCell(new DateTimeValue(gCal));
+ tableRow.addCell(new NumberValue(sumCoveredLine));
+ tableRow.addCell(new NumberValue(sumTotalLine));
+ tableRow.addCell(new NumberValue(percentage));
+ try {
+ dataTable.addRow(tableRow);
+ } catch (TypeMismatchException e) {
+ logger.log(Level.WARNING, "Invalid type! ");
+ }
+ }
+ });
+
+ return dataTable;
}
}
diff --git a/src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp b/src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp
index f17113d..9c85618 100644
--- a/src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp
+++ b/src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp
@@ -57,6 +57,138 @@
}, ${nonpassing});
search.addRunTypeCheckboxes(${showPresubmit}, ${showPostsubmit});
search.display();
+
+ var no_data_msg = "NO DATA";
+
+ $('#coverageModalGraph').modal({
+ width: '75%',
+ dismissible: true, // Modal can be dismissed by clicking outside of the modal
+ opacity: .5, // Opacity of modal background
+ inDuration: 300, // Transition in duration
+ outDuration: 200, // Transition out duration
+ startingTop: '4%', // Starting top style attribute
+ endingTop: '10%', // Ending top style attribute
+ ready: function(modal, trigger) { // Callback for Modal open. Modal and trigger parameters available.
+ var testname = modal.data('testname');
+ $('#coverageModalTitle').text("Code Coverage Chart : " + testname);
+ var query = new google.visualization.Query('show_coverage_overview?pageType=datatable&testName=' + testname);
+ // Send the query with a callback function.
+ query.send(handleQueryResponse);
+ },
+ complete: function() {
+ $('#coverage_combo_chart_div').empty();
+ $('#coverage_line_chart_div').empty();
+ $('#coverage_table_chart_div').empty();
+
+ $("div.valign-wrapper > h2.center-align:contains('" + no_data_msg + "')").each(function(index){
+ $(this).parent().remove();
+ });
+ $("span.indicator.badge.blue:contains('Graph')").each(function( index ) {
+ $(this).removeClass('blue');
+ $(this).addClass('grey');
+ });
+
+ $('#dataTableLoading').show("slow");
+ } // Callback for Modal close
+ }
+ );
+
+ // Handle the query response.
+ function handleQueryResponse(response) {
+ $('#dataTableLoading').hide("slow");
+ if (response.isError()) {
+ alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
+ return;
+ }
+ // Draw the visualization.
+ var data = response.getDataTable();
+ if (data.getNumberOfRows() == 0) {
+ var blankData = '<div class="valign-wrapper" style="height: 90%;">'
+ + '<h2 class="center-align" style="width: 100%;">' + no_data_msg + '</h2>'
+ + '</div>';
+ $('#coverageModalTitle').after(blankData);
+ return;
+ }
+ data.sort([{column: 0}]);
+
+ var date_formatter = new google.visualization.DateFormat({ pattern: "yyyy-MM-dd" });
+ date_formatter.format(data, 0);
+
+ var dataView = new google.visualization.DataView(data);
+
+ // Disable coveredLine and totalLine
+ dataView.hideColumns([1,2]);
+
+ var lineOptions = {
+ title: 'Source Code Line Coverage',
+ width: '100%',
+ height: 450,
+ curveType: 'function',
+ intervals: { 'color' : 'series-color' },
+ interval: {
+ 'fill': {
+ 'style': 'area',
+ 'curveType': 'function',
+ 'fillOpacity': 0.2
+ },
+ 'bar': {
+ 'style': 'bars',
+ 'barWidth': 0,
+ 'lineWidth': 1,
+ 'pointSize': 3,
+ 'fillOpacity': 1
+ }},
+ legend: { position: 'bottom' },
+ tooltip: { isHtml: true },
+ fontName: 'Roboto',
+ titleTextStyle: {
+ color: '#757575',
+ fontSize: 16,
+ bold: false
+ },
+ pointsVisible: true,
+ vAxis:{
+ title: 'Code Coverage Ratio (%)',
+ titleTextStyle: {
+ color: '#424242',
+ fontSize: 12,
+ italic: false
+ },
+ textStyle: {
+ fontSize: 12,
+ color: '#757575'
+ },
+ },
+ hAxis: {
+ title: 'Date',
+ format: 'yyyy-MM-dd',
+ minTextSpacing: 0,
+ showTextEvery: 1,
+ slantedText: true,
+ slantedTextAngle: 45,
+ textStyle: {
+ fontSize: 12,
+ color: '#757575'
+ },
+ titleTextStyle: {
+ color: '#424242',
+ fontSize: 12,
+ italic: false
+ }
+ },
+ };
+ var lineChart = new google.visualization.LineChart(document.getElementById('coverage_line_chart_div'));
+ lineChart.draw(dataView, lineOptions);
+
+ var tableOptions = {
+ title: 'Covered/Total Source Code Line Count (SLOC)',
+ width: '95%',
+ // height: 350,
+ is3D: true
+ };
+ var tableChart = new google.visualization.Table(document.getElementById('coverage_table_chart_div'));
+ tableChart.draw(data, tableOptions);
+ }
});
// draw test statistics chart
@@ -195,9 +327,39 @@
</div>
</div>
</div>
- <div class='col s12' id='test-results-container'>
+ <div class='col s12' id='test-results-container'></div>
+ </div>
+
+ <!-- Modal Structure -->
+ <div id="coverageModalGraph" class="modal modal-fixed-footer" style="width: 75%;">
+ <div class="modal-content">
+ <h4 id="coverageModalTitle">Code Coverage Chart</h4>
+
+ <div class="preloader-wrapper big active loaders">
+ <div id="dataTableLoading" class="spinner-layer spinner-blue-only">
+ <div class="circle-clipper left">
+ <div class="circle"></div>
+ </div>
+ <div class="gap-patch">
+ <div class="circle"></div>
+ </div>
+ <div class="circle-clipper right">
+ <div class="circle"></div>
+ </div>
+ </div>
+ </div>
+
+ <!--Div that will hold the visualization graph -->
+ <div id="coverage_line_chart_div"></div>
+ <p></p>
+ <p></p>
+ <div id="coverage_table_chart_div" class="center-align"></div>
+ </div>
+ <div class="modal-footer">
+ <a href="#!" class="modal-action modal-close waves-effect waves-green btn-flat ">Close</a>
</div>
</div>
+
<%@ include file="footer.jsp" %>
</body>
</html>
diff --git a/src/main/webapp/css/test_results.css b/src/main/webapp/css/test_results.css
index fbcd49a..1e7c13b 100644
--- a/src/main/webapp/css/test_results.css
+++ b/src/main/webapp/css/test_results.css
@@ -74,6 +74,14 @@ li.test-run-container.active {
padding: 4px 15px;
margin: 0;
}
+.loaders {
+ position: absolute;
+ left:0;
+ right:0;
+ top:0;
+ bottom:0;
+ margin:auto;
+}
.indicator {
color: white;
font-size: 12px;
diff --git a/src/main/webapp/js/test_results.js b/src/main/webapp/js/test_results.js
index 5109d8d..a7be803 100644
--- a/src/main/webapp/js/test_results.js
+++ b/src/main/webapp/js/test_results.js
@@ -266,6 +266,19 @@
return false;
});
}
+ if ($('#coverageModalGraph').length) {
+ createClickableIndicator(
+ div, 'Graph', 'grey lighten-1',
+ function(evt) {
+ $('#coverageModalGraph').data("testname", test);
+ $('#coverageModalGraph').modal('open');
+ $(evt.target).removeClass("grey");
+ $(evt.target).addClass("blue");
+ return false;
+ }
+ );
+ }
+
var expand = $('<i></i>');
expand.addClass('material-icons expand-arrow')
expand.text('expand_more');