diff options
author | Young Gyu Park <younggyu@google.com> | 2018-06-26 11:36:17 +0900 |
---|---|---|
committer | Young Gyu Park <younggyu@google.com> | 2018-07-11 10:45:31 +0900 |
commit | c0204c1c2c600bb21515207356d6f5a2626153d7 (patch) | |
tree | 2fde7ed4011683dba452ad1f05f06c8cbaf841c5 /src | |
parent | f967235ef688948f3aef233424966f6b92558772 (diff) | |
download | dashboard-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.java | 214 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/jsp/show_coverage_overview.jsp | 164 | ||||
-rw-r--r-- | src/main/webapp/css/test_results.css | 8 | ||||
-rw-r--r-- | src/main/webapp/js/test_results.js | 13 |
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'); |