Add coloring to Test Planner machine view table cells.
Signed-off-by: James Ren <jamesren@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@4485 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/client/src/autotest/common/AbstractStatusSummary.java b/frontend/client/src/autotest/common/AbstractStatusSummary.java
new file mode 100644
index 0000000..c130003
--- /dev/null
+++ b/frontend/client/src/autotest/common/AbstractStatusSummary.java
@@ -0,0 +1,58 @@
+package autotest.common;
+
+
+public abstract class AbstractStatusSummary {
+ public static final String BLANK_COLOR = "status_blank";
+ private static final ColorMapping[] CELL_COLOR_MAP = {
+ // must be in descending order of percentage
+ new ColorMapping(95, "status_95"),
+ new ColorMapping(90, "status_90"),
+ new ColorMapping(85, "status_85"),
+ new ColorMapping(75, "status_75"),
+ new ColorMapping(1, "status_bad"),
+ new ColorMapping(0, "status_none"),
+ };
+
+ /**
+ * Stores a CSS class for pass rates and the minimum passing percentage required
+ * to have that class.
+ */
+ private static class ColorMapping {
+ // store percentage as int so we can reprint it consistently
+ public int minPercent;
+ public String cssClass;
+
+ public ColorMapping(int minPercent, String cssClass) {
+ this.minPercent = minPercent;
+ this.cssClass = cssClass;
+ }
+
+ public boolean matches(double ratio) {
+ return ratio * 100 >= minPercent;
+ }
+ }
+
+ public String formatStatusCounts() {
+ String text = getPassed() + " / " + getComplete();
+ if (getIncomplete() > 0) {
+ text += " (" + getIncomplete() + " incomplete)";
+ }
+ return text;
+ }
+
+ public String getCssClass() {
+ if (getComplete() == 0) {
+ return BLANK_COLOR;
+ }
+ double ratio = (double) getPassed() / getComplete();
+ for (ColorMapping mapping : CELL_COLOR_MAP) {
+ if (mapping.matches(ratio))
+ return mapping.cssClass;
+ }
+ throw new RuntimeException("No color map match for ratio " + ratio);
+ }
+
+ protected abstract int getPassed();
+ protected abstract int getComplete();
+ protected abstract int getIncomplete();
+}
diff --git a/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java b/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java
index 822ac22..381c0b6 100644
--- a/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java
+++ b/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java
@@ -33,7 +33,7 @@
public class Spreadsheet extends Composite
implements ScrollHandler, ClickHandler, ContextMenuHandler {
-
+
private static final int MIN_TABLE_SIZE_PX = 90;
private static final int WINDOW_BORDER_PX = 15;
private static final int SCROLLBAR_FUDGE = 16;
@@ -42,7 +42,7 @@
private static final int TD_BORDER_PX = 1;
private static final String HIGHLIGHTED_CLASS = "highlighted";
private static final int CELLS_PER_ITERATION = 1000;
-
+
private Header rowFields, columnFields;
private List<Header> rowHeaderValues = new ArrayList<Header>();
private List<Header> columnHeaderValues = new ArrayList<Header>();
@@ -57,13 +57,13 @@
private Panel rowHeadersClipPanel, columnHeadersClipPanel;
private ScrollPanel scrollPanel = new ScrollPanel(dataTable);
private TableRenderer renderer = new TableRenderer();
-
+
private SpreadsheetListener listener;
-
+
public interface SpreadsheetListener {
public void onCellClicked(CellInfo cellInfo, boolean isRightClick);
}
-
+
public static interface Header extends List<String> {}
public static class HeaderImpl extends ArrayList<String> implements Header {
public HeaderImpl() {
@@ -77,49 +77,49 @@
return new HeaderImpl(baseType);
}
}
-
+
public static class CellInfo {
public Header row, column;
public String contents;
- public String color;
+ public String cssClass;
public Integer widthPx, heightPx;
public int rowSpan = 1, colSpan = 1;
public int testCount = 0;
public int testIndex;
-
+
public CellInfo(Header row, Header column, String contents) {
this.row = row;
this.column = column;
this.contents = contents;
}
-
+
public boolean isHeader() {
return !isEmpty() && (row == null || column == null);
}
-
+
public boolean isEmpty() {
return row == null && column == null;
}
}
-
+
private class RenderCommand implements IncrementalCommand {
private int state = 0;
private int rowIndex = 0;
private IncrementalCommand onFinished;
-
+
public RenderCommand(IncrementalCommand onFinished) {
this.onFinished = onFinished;
}
-
+
private void renderSomeRows() {
- renderer.renderRowsAndAppend(dataTable, dataCells,
+ renderer.renderRowsAndAppend(dataTable, dataCells,
rowIndex, rowsPerIteration, true);
rowIndex += rowsPerIteration;
if (rowIndex > dataCells.length) {
state++;
}
}
-
+
public boolean execute() {
switch (state) {
case 0:
@@ -163,7 +163,7 @@
DeferredCommand.addCommand(onFinished);
return false;
}
-
+
state++;
return true;
}
@@ -172,29 +172,29 @@
public Spreadsheet() {
dataTable.setStyleName("spreadsheet-data");
killPaddingAndSpacing(dataTable);
-
+
rowHeaders.setStyleName("spreadsheet-headers");
killPaddingAndSpacing(rowHeaders);
rowHeadersClipPanel = wrapWithClipper(rowHeaders);
-
+
columnHeaders.setStyleName("spreadsheet-headers");
killPaddingAndSpacing(columnHeaders);
columnHeadersClipPanel = wrapWithClipper(columnHeaders);
-
+
scrollPanel.setStyleName("spreadsheet-scroller");
scrollPanel.setAlwaysShowScrollBars(true);
scrollPanel.addScrollHandler(this);
-
+
parentTable.setStyleName("spreadsheet-parent");
killPaddingAndSpacing(parentTable);
parentTable.setWidget(0, 1, columnHeadersClipPanel);
parentTable.setWidget(1, 0, rowHeadersClipPanel);
parentTable.setWidget(1, 1, scrollPanel);
-
+
setupTableInput(dataTable);
setupTableInput(rowHeaders);
setupTableInput(columnHeaders);
-
+
initWidget(parentTable);
}
@@ -207,7 +207,7 @@
table.setCellSpacing(0);
table.setCellPadding(0);
}
-
+
/*
* Wrap a widget with a panel that will clip its contents rather than grow
* too much.
@@ -218,12 +218,12 @@
wrapper.setStyleName("clipper");
return wrapper;
}
-
+
public void setHeaderFields(Header rowFields, Header columnFields) {
this.rowFields = rowFields;
this.columnFields = columnFields;
}
-
+
private void addHeader(List<Header> headerList, Map<Header, Integer> headerMap,
List<String> header) {
Header headerObject = HeaderImpl.fromBaseType(header);
@@ -231,28 +231,28 @@
headerList.add(headerObject);
headerMap.put(headerObject, headerMap.size());
}
-
+
public void addRowHeader(List<String> header) {
addHeader(rowHeaderValues, rowHeaderMap, header);
}
-
+
public void addColumnHeader(List<String> header) {
addHeader(columnHeaderValues, columnHeaderMap, header);
}
-
+
private int getHeaderPosition(Map<Header, Integer> headerMap, Header header) {
assert headerMap.containsKey(header);
return headerMap.get(header);
}
-
+
private int getRowPosition(Header rowHeader) {
return getHeaderPosition(rowHeaderMap, rowHeader);
}
-
+
private int getColumnPosition(Header columnHeader) {
return getHeaderPosition(columnHeaderMap, columnHeader);
}
-
+
/**
* Must be called after adding headers but before adding data
*/
@@ -268,14 +268,14 @@
}
return dataCells[row][column];
}
-
+
private CellInfo getCellInfo(CellInfo[][] cells, int row, int column) {
if (cells[row][column] == null) {
cells[row][column] = new CellInfo(null, null, " ");
}
return cells[row][column];
}
-
+
/**
* Render the data into HTML tables. Done through a deferred command.
*/
@@ -287,25 +287,25 @@
renderer.renderRows(rowHeaders, rowHeaderCells, false);
renderer.renderRows(columnHeaders, columnHeaderCells, false);
}
-
+
public void computeRowsPerIteration() {
int cellsPerRow = columnHeaderValues.size();
rowsPerIteration = Math.max(CELLS_PER_ITERATION / cellsPerRow, 1);
dataTable.setRowsPerFragment(rowsPerIteration);
}
-
+
private void computeHeaderCells() {
rowHeaderCells = new CellInfo[rowHeaderValues.size()][rowFields.size()];
fillHeaderCells(rowHeaderCells, rowFields, rowHeaderValues, true);
-
+
columnHeaderCells = new CellInfo[columnFields.size()][columnHeaderValues.size()];
fillHeaderCells(columnHeaderCells, columnFields, columnHeaderValues, false);
}
-
+
/**
* TODO (post-1.0) - this method needs good cleanup and documentation
*/
- private void fillHeaderCells(CellInfo[][] cells, Header fields, List<Header> headerValues,
+ private void fillHeaderCells(CellInfo[][] cells, Header fields, List<Header> headerValues,
boolean isRows) {
int headerSize = fields.size();
String[] lastFieldValue = new String[headerSize];
@@ -338,7 +338,7 @@
}
}
}
-
+
private String formatHeader(String field, String value) {
if (value.equals("")) {
return BLANK_STRING;
@@ -375,7 +375,7 @@
getCellInfo(to, row, lastColumn).heightPx = height - 2 * CELL_PADDING_PX;
}
}
-
+
private void matchColumnWidths(HTMLTable from, CellInfo[][] to) {
int lastToRow = to.length - 1;
int lastFromRow = from.getRowCount() - 1;
@@ -384,7 +384,7 @@
getCellInfo(to, lastToRow, column).widthPx = width - 2 * CELL_PADDING_PX;
}
}
-
+
protected String getTableCellText(HTMLTable table, int row, int column) {
Element td = table.getCellFormatter().getElement(row, column);
Element div = td.getFirstChildElement();
@@ -403,27 +403,27 @@
columnHeaderMap.clear();
dataCells = rowHeaderCells = columnHeaderCells = null;
dataTable.reset();
-
+
setRowHeadersOffset(0);
setColumnHeadersOffset(0);
}
-
+
/**
* Make the spreadsheet fill the available window space to the right and bottom
* of its position.
*/
public void fillWindow(boolean useTableSize) {
- int newHeightPx = Window.getClientHeight() - (columnHeaders.getAbsoluteTop() +
+ int newHeightPx = Window.getClientHeight() - (columnHeaders.getAbsoluteTop() +
columnHeaders.getOffsetHeight());
newHeightPx = adjustMaxDimension(newHeightPx);
- int newWidthPx = Window.getClientWidth() - (rowHeaders.getAbsoluteLeft() +
+ int newWidthPx = Window.getClientWidth() - (rowHeaders.getAbsoluteLeft() +
rowHeaders.getOffsetWidth());
newWidthPx = adjustMaxDimension(newWidthPx);
if (useTableSize) {
newHeightPx = Math.min(newHeightPx, rowHeaders.getOffsetHeight());
newWidthPx = Math.min(newWidthPx, columnHeaders.getOffsetWidth());
}
-
+
// apply the changes all together
rowHeadersClipPanel.setHeight(getSizePxString(newHeightPx));
columnHeadersClipPanel.setWidth(getSizePxString(newWidthPx));
@@ -432,18 +432,18 @@
}
/**
- * Adjust a maximum table dimension to allow room for edge decoration and
+ * Adjust a maximum table dimension to allow room for edge decoration and
* always maintain a minimum height
*/
protected int adjustMaxDimension(int maxDimensionPx) {
- return Math.max(maxDimensionPx - WINDOW_BORDER_PX - SCROLLBAR_FUDGE,
+ return Math.max(maxDimensionPx - WINDOW_BORDER_PX - SCROLLBAR_FUDGE,
MIN_TABLE_SIZE_PX);
}
protected String getSizePxString(int sizePx) {
return sizePx + "px";
}
-
+
/**
* Ensure the row header clip panel allows the full width of the row headers
* to display.
@@ -452,11 +452,11 @@
int width = rowHeaders.getOffsetWidth();
rowHeadersClipPanel.setWidth(getSizePxString(width));
}
-
+
private Element getCellElement(HTMLTable table, int row, int column) {
return table.getCellFormatter().getElement(row, column);
}
-
+
private Element getCellElement(CellInfo cellInfo) {
assert cellInfo.row != null || cellInfo.column != null;
Element tdElement;
@@ -465,25 +465,25 @@
} else if (cellInfo.column == null) {
tdElement = getCellElement(rowHeaders, getRowPosition(cellInfo.row), 0);
} else {
- tdElement = getCellElement(dataTable, getRowPosition(cellInfo.row),
+ tdElement = getCellElement(dataTable, getRowPosition(cellInfo.row),
getColumnPosition(cellInfo.column));
}
Element cellElement = tdElement.getFirstChildElement();
assert cellElement != null;
return cellElement;
}
-
+
protected int getColumnWidth(HTMLTable table, int column) {
// using the column formatter doesn't seem to work
int numRows = table.getRowCount();
- return table.getCellFormatter().getElement(numRows - 1, column).getOffsetWidth() -
+ return table.getCellFormatter().getElement(numRows - 1, column).getOffsetWidth() -
TD_BORDER_PX;
}
-
+
protected int getRowHeight(HTMLTable table, int row) {
// see getColumnWidth()
int numCols = table.getCellCount(row);
- return table.getCellFormatter().getElement(row, numCols - 1).getOffsetHeight() -
+ return table.getCellFormatter().getElement(row, numCols - 1).getOffsetHeight() -
TD_BORDER_PX;
}
@@ -494,7 +494,7 @@
public void onScroll(ScrollEvent event) {
int scrollLeft = scrollPanel.getHorizontalScrollPosition();
int scrollTop = scrollPanel.getScrollPosition();
-
+
setColumnHeadersOffset(-scrollLeft);
setRowHeadersOffset(-scrollTop);
}
@@ -506,26 +506,26 @@
protected void setColumnHeadersOffset(int offset) {
columnHeaders.getElement().getStyle().setPropertyPx("left", offset);
}
-
+
@Override
public void onClick(ClickEvent event) {
handleEvent(event, false);
}
-
+
@Override
public void onContextMenu(ContextMenuEvent event) {
handleEvent(event, true);
}
-
+
private void handleEvent(DomEvent<?> event, boolean isRightClick) {
if (listener == null)
return;
-
+
assert event.getSource() instanceof RightClickTable;
HTMLTable.Cell tableCell = ((RightClickTable) event.getSource()).getCellForDomEvent(event);
int row = tableCell.getRowIndex();
int column = tableCell.getCellIndex();
-
+
CellInfo[][] cells;
if (event.getSource() == rowHeaders) {
cells = rowHeaderCells;
@@ -541,12 +541,12 @@
CellInfo cell = cells[row][column];
if (cell == null || cell.isEmpty())
return; // don't report clicks on empty cells
-
+
listener.onCellClicked(cell, isRightClick);
}
/**
- * In HTMLTables, a cell with rowspan > 1 won't count in column indices for the extra rows it
+ * In HTMLTables, a cell with rowspan > 1 won't count in column indices for the extra rows it
* spans, which will mess up column indices for other cells in those rows. This method adjusts
* the column index passed to onCellClicked() to account for that.
*/
@@ -556,7 +556,7 @@
return i + column;
}
}
-
+
throw new RuntimeException("Failed to find non-null cell");
}
@@ -572,7 +572,7 @@
cellElement.setClassName("");
}
}
-
+
public List<Integer> getAllTestIndices() {
List<Integer> testIndices = new ArrayList<Integer>();
diff --git a/frontend/client/src/autotest/common/table/TableRenderer.java b/frontend/client/src/autotest/common/table/TableRenderer.java
index 1641dfb..5384cd3 100644
--- a/frontend/client/src/autotest/common/table/TableRenderer.java
+++ b/frontend/client/src/autotest/common/table/TableRenderer.java
@@ -13,14 +13,14 @@
// min-width/min-height aren't supported in the hosted mode browser
public static final String SIZE_PREFIX = GWT.isScript() ? "min-" : "";
private static final String NONCLICKABLE_CLASS = "spreadsheet-cell-nonclickable";
-
+
protected String attributeString(String attribute, String value) {
if (value.equals(""))
return "";
return " " + attribute + "=\"" + value + "\"";
}
-
- public void renderRowsAndAppend(HTMLTable tableObject, CellInfo[][] rows,
+
+ public void renderRowsAndAppend(HTMLTable tableObject, CellInfo[][] rows,
int startRow, int maxRows, boolean renderNull) {
StringBuffer htmlBuffer= new StringBuffer();
htmlBuffer.append("<table><tbody>");
@@ -33,17 +33,16 @@
htmlBuffer.append("<td> </td>");
} else if (cell != null) {
String tdAttributes = "", divAttributes = "", divStyle = "";
- if (cell.color != null) {
- tdAttributes += attributeString("style",
- "background-color: " + cell.color + ";");
+ if (cell.cssClass != null) {
+ tdAttributes += attributeString("class", cell.cssClass);
}
if (cell.rowSpan > 1) {
- tdAttributes += attributeString("rowspan", Integer.toString(cell.rowSpan));
+ tdAttributes += attributeString("rowspan", Integer.toString(cell.rowSpan));
}
if (cell.colSpan > 1) {
- tdAttributes += attributeString("colspan", Integer.toString(cell.colSpan));
+ tdAttributes += attributeString("colspan", Integer.toString(cell.colSpan));
}
-
+
if (cell.widthPx != null) {
divStyle += SIZE_PREFIX + "width: " + cell.widthPx + "px; ";
}
@@ -56,7 +55,7 @@
if (cell.isEmpty()) {
divAttributes += attributeString("class", NONCLICKABLE_CLASS);
}
-
+
htmlBuffer.append("<td " + tdAttributes + ">");
htmlBuffer.append("<div " + divAttributes + ">");
htmlBuffer.append(cell.contents);
@@ -66,15 +65,15 @@
htmlBuffer.append("</tr>");
}
htmlBuffer.append("</tbody></table>");
-
+
renderBody(tableObject, htmlBuffer.toString());
}
-
+
public void renderRows(HTMLTable tableObject, CellInfo[][] rows, boolean renderNull) {
DomUtils.clearDomChildren(tableObject.getElement()); // remove existing tbodies
renderRowsAndAppend(tableObject, rows, 0, rows.length, renderNull);
}
-
+
public void renderRows(HTMLTable tableObject, CellInfo[][] rows) {
renderRows(tableObject, rows, true);
}
@@ -83,15 +82,15 @@
// render the table within a DIV
Element tempDiv = DOM.createDiv();
tempDiv.setInnerHTML(html);
-
+
// inject the new tbody into the existing table
Element newTable = tempDiv.getFirstChildElement();
Element newBody = newTable.getFirstChildElement();
tableObject.getElement().appendChild(newBody);
-
+
setBodyElement(tableObject, newBody);
}
-
+
/**
* A little hack to set the private member variable bodyElem of an HTMLTable.
*/
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewTable.java b/frontend/client/src/autotest/planner/machine/MachineViewTable.java
index 57bd8a9..b315892 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewTable.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewTable.java
@@ -1,5 +1,6 @@
package autotest.planner.machine;
+import autotest.common.AbstractStatusSummary;
import autotest.common.Utils;
import com.google.gwt.json.client.JSONArray;
@@ -15,49 +16,54 @@
import java.util.TreeSet;
public class MachineViewTable {
+ public static class RowDisplay {
+ String content;
+ String cssClass = AbstractStatusSummary.BLANK_COLOR;
- public static class PassRate {
- int passed;
- int total;
+ public RowDisplay(String content) {
+ this.content = content;
+ }
+
+ public RowDisplay(String content, String cssClass) {
+ this.content = content;
+ this.cssClass = cssClass;
+ }
}
public static class Row {
String machine;
String status;
- Map<String, PassRate> passRates;
+ Map<String, StatusSummary> statusSummaries;
List<String> bugIds;
private Row(String machine, String status,
- Map<String, PassRate> passRates, List<String> bugIds) {
+ Map<String, StatusSummary> statusSummaries, List<String> bugIds) {
this.machine = machine;
this.status = status;
- this.passRates = passRates;
+ this.statusSummaries = statusSummaries;
this.bugIds = bugIds;
}
public static Row fromJsonObject(JSONObject rowObject) {
- Map<String, PassRate> passRates = new HashMap<String, PassRate>();
+ Map<String, StatusSummary> statusSummaries = new HashMap<String, StatusSummary>();
JSONArray testsRun = rowObject.get("tests_run").isArray();
for (int i = 0; i < testsRun.size(); i++) {
JSONObject test = testsRun.get(i).isObject();
String testName = Utils.jsonToString(test.get("test_name"));
- PassRate passRate = passRates.get(testName);
- if (passRate == null) {
- passRate = new PassRate();
- passRates.put(testName, passRate);
+ StatusSummary statusSummary = statusSummaries.get(testName);
+ if (statusSummary == null) {
+ statusSummary = new StatusSummary();
+ statusSummaries.put(testName, statusSummary);
}
- passRate.total++;
- if (test.get("success").isBoolean().booleanValue()) {
- passRate.passed++;
- }
+ statusSummary.addStatus(Utils.jsonToString(test.get("status")));
}
return new Row(Utils.jsonToString(rowObject.get("machine")),
Utils.jsonToString(rowObject.get("status")),
- passRates,
+ statusSummaries,
Arrays.asList(Utils.JSONtoStrings(rowObject.get("bug_ids").isArray())));
}
}
@@ -65,7 +71,7 @@
public static interface Display {
public void clearData();
public void setHeaders(Collection<String> headers);
- public void addRow(Collection<String> rowData);
+ public void addRow(Collection<RowDisplay> rowData);
public void finalRender();
}
@@ -88,7 +94,7 @@
private void displayData() {
Set<String> allTestNames = new TreeSet<String>();
for (Row row : rows) {
- allTestNames.addAll(row.passRates.keySet());
+ allTestNames.addAll(row.statusSummaries.keySet());
}
List<String> headers = new ArrayList<String>();
@@ -99,20 +105,21 @@
display.setHeaders(headers);
for (Row row : rows) {
- List<String> rowData = new ArrayList<String>();
- rowData.add(row.machine);
- rowData.add(row.status);
+ List<RowDisplay> rowData = new ArrayList<RowDisplay>();
+ rowData.add(new RowDisplay(row.machine));
+ rowData.add(new RowDisplay(row.status));
for (String testName : allTestNames) {
- PassRate passRate = row.passRates.get(testName);
- if (passRate != null) {
- rowData.add(passRate.passed + "/" + passRate.total);
+ StatusSummary statusSummary = row.statusSummaries.get(testName);
+ if (statusSummary != null) {
+ rowData.add(new RowDisplay(
+ statusSummary.formatStatusCounts(), statusSummary.getCssClass()));
} else {
- rowData.add("");
+ rowData.add(new RowDisplay(""));
}
}
- rowData.add(String.valueOf(row.bugIds.size()));
+ rowData.add(new RowDisplay(String.valueOf(row.bugIds.size())));
display.addRow(rowData);
}
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java b/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
index c970bf1..069fe58 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
@@ -1,6 +1,7 @@
package autotest.planner.machine;
import autotest.planner.TestPlannerUtils;
+import autotest.planner.machine.MachineViewTable.RowDisplay;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
@@ -26,6 +27,7 @@
scrollTable.setSortPolicy(SortPolicy.DISABLED);
scrollTable.setResizePolicy(ResizePolicy.UNCONSTRAINED);
scrollTable.setScrollPolicy(ScrollPolicy.BOTH);
+ dataTable.setSelectionEnabled(false);
scrollTable.setSize("100%", TestPlannerUtils.getHeightParam(Window.getClientHeight()));
scrollTable.setVisible(false);
@@ -34,14 +36,15 @@
}
@Override
- public void addRow(Collection<String> rowData) {
+ public void addRow(Collection<RowDisplay> rowData) {
assert rowData.size() == dataTable.getColumnCount();
int row = dataTable.insertRow(dataTable.getRowCount());
int column = 0;
- for (String data : rowData) {
- dataTable.setText(row, column++, data);
+ for (RowDisplay data : rowData) {
+ dataTable.setText(row, column, data.content);
+ dataTable.getCellFormatter().addStyleName(row, column++, data.cssClass);
}
}
diff --git a/frontend/client/src/autotest/planner/machine/StatusSummary.java b/frontend/client/src/autotest/planner/machine/StatusSummary.java
new file mode 100644
index 0000000..59d45ba
--- /dev/null
+++ b/frontend/client/src/autotest/planner/machine/StatusSummary.java
@@ -0,0 +1,36 @@
+package autotest.planner.machine;
+
+import autotest.common.AbstractStatusSummary;
+
+class StatusSummary extends AbstractStatusSummary {
+ static final String GOOD_STATUS = "GOOD";
+ static final String RUNNING_STATUS = "RUNNING";
+
+ private int complete;
+ private int incomplete;
+ private int passed;
+
+ void addStatus(String status) {
+ if (status.equals(GOOD_STATUS)) {
+ complete++;
+ passed++;
+ } else if (status.equals(RUNNING_STATUS)) {
+ incomplete++;
+ } else {
+ complete++;
+ }
+ }
+
+ @Override
+ protected int getComplete() {
+ return complete;
+ }
+ @Override
+ protected int getIncomplete() {
+ return incomplete;
+ }
+ @Override
+ protected int getPassed() {
+ return passed;
+ }
+}
diff --git a/frontend/client/src/autotest/public/common.css b/frontend/client/src/autotest/public/common.css
index 65fe13c..dcbd94a 100644
--- a/frontend/client/src/autotest/public/common.css
+++ b/frontend/client/src/autotest/public/common.css
@@ -35,3 +35,30 @@
font-family: Courier New, Courier, monospace;
}
+td .status_blank {
+ background-color: #ffffff;
+}
+
+td .status_none {
+ background-color: #d080d0;
+}
+
+td .status_bad {
+ background-color: #ff4040;
+}
+
+td .status_75 {
+ background-color: #ffc040;
+}
+
+td .status_85 {
+ background-color: #ffff00;
+}
+
+td .status_90 {
+ background-color: #c0ff80;
+}
+
+td .status_95 {
+ background-color: #32CD32;
+}
diff --git a/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java b/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
index bae2f06..66445f8 100644
--- a/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
+++ b/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
@@ -22,7 +22,7 @@
private static final NotifyManager notifyManager = NotifyManager.getInstance();
private static final int MAX_CELL_COUNT = 500000;
private static final int ROWS_PROCESSED_PER_ITERATION = 1000;
-
+
private Spreadsheet spreadsheet;
private TestGroupDataSource dataSource;
private int numTotalTests;
@@ -32,7 +32,7 @@
private Command onFinished;
private Duration timer;
private Query currentQuery;
-
+
public static class TooManyCellsError extends Exception {
public int cellCount;
@@ -41,16 +41,16 @@
this.cellCount = cellCount;
}
}
-
+
private class ProcessDataCommand implements IncrementalCommand {
private int state = 0;
private List<JSONObject> counts;
private int currentRow = 0;
-
+
public ProcessDataCommand(List<JSONObject> counts) {
this.counts = counts;
}
-
+
public void processSomeRows() {
for (int i = 0; i < ROWS_PROCESSED_PER_ITERATION; i++, currentRow++) {
if (currentRow == counts.size()) {
@@ -60,7 +60,7 @@
processRow(counts.get(currentRow).isObject());
}
}
-
+
public boolean execute() {
switch (state) {
case 0:
@@ -82,7 +82,7 @@
processSomeRows();
return true;
case 3:
- // we must make the spreadsheet visible before rendering, or size computations
+ // we must make the spreadsheet visible before rendering, or size computations
// won't work correctly
spreadsheet.setVisible(true);
break;
@@ -95,7 +95,7 @@
finalizeCommand();
return false;
}
-
+
state++;
return true;
}
@@ -105,7 +105,7 @@
onFinished.execute();
}
}
-
+
public SpreadsheetDataProcessor(Spreadsheet spreadsheet) {
this.spreadsheet = spreadsheet;
}
@@ -134,18 +134,18 @@
StatusSummary statusSummary = StatusSummary.getStatusSummary(group);
numTotalTests += statusSummary.getTotal();
cellInfo.contents = statusSummary.formatContents();
- cellInfo.color = statusSummary.getColor();
+ cellInfo.cssClass = statusSummary.getCssClass();
cellInfo.testCount = statusSummary.getTotal();
cellInfo.testIndex = (int) group.get("test_idx").isNumber().doubleValue();
lastCellInfo = cellInfo;
}
-
+
public void refresh(JSONObject condition, Command onFinished) {
timer = new Duration();
this.onFinished = onFinished;
dataSource.query(condition, this);
}
-
+
public void onQueryReady(Query query) {
currentQuery = query;
query.getPage(null, null, null, this);
@@ -172,7 +172,7 @@
public int getNumTotalTests() {
return numTotalTests;
}
-
+
/**
* This is useful when there turns out to be only a single test return.
* @return the last CellInfo created. Should only really be called when there was only a single
@@ -182,16 +182,16 @@
assert numTotalTests == 1;
return lastCellInfo;
}
-
+
public void onError(JSONObject errorObject) {
onFinished.execute();
}
- public void setHeaders(List<HeaderField> rowFields, List<HeaderField> columnFields,
+ public void setHeaders(List<HeaderField> rowFields, List<HeaderField> columnFields,
JSONObject queryParameters) {
this.rowFields = getHeaderSqlNames(rowFields);
this.columnFields = getHeaderSqlNames(columnFields);
-
+
List<List<String>> headerGroups = new ArrayList<List<String>>();
headerGroups.add(this.rowFields);
headerGroups.add(this.columnFields);
@@ -214,7 +214,7 @@
public TestGroupDataSource getDataSource() {
return dataSource;
}
-
+
public Query getCurrentQuery() {
return currentQuery;
}
diff --git a/frontend/client/src/autotest/tko/StatusSummary.java b/frontend/client/src/autotest/tko/StatusSummary.java
index eba22a0..00602df 100644
--- a/frontend/client/src/autotest/tko/StatusSummary.java
+++ b/frontend/client/src/autotest/tko/StatusSummary.java
@@ -2,105 +2,71 @@
package autotest.tko;
+import autotest.common.AbstractStatusSummary;
import autotest.common.Utils;
import com.google.gwt.json.client.JSONObject;
import java.util.Arrays;
-class StatusSummary {
- static final String BLANK_COLOR = "#FFFFFF";
- static final ColorMapping[] CELL_COLOR_MAP = {
- // must be in descending order of percentage
- new ColorMapping(95, "#32CD32"),
- new ColorMapping(90, "#c0ff80"),
- new ColorMapping(85, "#ffff00"),
- new ColorMapping(75, "#ffc040"),
- new ColorMapping(1, "#ff4040"),
- new ColorMapping(0, "#d080d0"),
- };
-
+class StatusSummary extends AbstractStatusSummary {
public int passed = 0;
public int complete = 0;
public int incomplete = 0;
public int total = 0; // TEST_NA is included here, but not in any other
-
+
private String[] contents = null;
-
- /**
- * Stores a color for pass rates and the minimum passing percentage required
- * to have that color.
- */
- static class ColorMapping {
- // store percentage as int so we can reprint it consistently
- public int minPercent;
- public String htmlColor;
-
- public ColorMapping(int minPercent, String htmlColor) {
- this.minPercent = minPercent;
- this.htmlColor = htmlColor;
- }
-
- public boolean matches(double ratio) {
- return ratio * 100 >= minPercent;
- }
- }
-
+
public static StatusSummary getStatusSummary(JSONObject group) {
StatusSummary summary = new StatusSummary();
summary.passed = getField(group, TestGroupDataSource.PASS_COUNT_FIELD);
summary.complete = getField(group, TestGroupDataSource.COMPLETE_COUNT_FIELD);
summary.incomplete = getField(group, TestGroupDataSource.INCOMPLETE_COUNT_FIELD);
summary.total = getField(group, TestGroupDataSource.GROUP_COUNT_FIELD);
-
+
if (group.containsKey("extra_info")) {
summary.contents = Utils.JSONtoStrings(group.get("extra_info").isArray());
}
-
+
return summary;
}
private static int getField(JSONObject group, String field) {
return (int) group.get(field).isNumber().doubleValue();
}
-
+
/**
* Force construction to go through getStatusSummary() factory method.
*/
private StatusSummary() {}
-
+
public int getTotal() {
return total;
}
public String formatContents() {
String result = formatStatusCounts();
-
+
if (contents != null) {
result += "<br>";
result += Utils.joinStrings("<br>", Arrays.asList(contents), true);
}
-
+
return result;
}
-
- private String formatStatusCounts() {
- String text = passed + " / " + complete;
- if (incomplete > 0) {
- text += " (" + incomplete + " incomplete)";
- }
- return text;
+
+ @Override
+ protected int getComplete() {
+ return complete;
}
- public String getColor() {
- if (complete == 0) {
- return BLANK_COLOR;
- }
- double ratio = (double) passed / complete;
- for (ColorMapping mapping : CELL_COLOR_MAP) {
- if (mapping.matches(ratio))
- return mapping.htmlColor;
- }
- throw new RuntimeException("No color map match for ratio " + ratio);
+ @Override
+ protected int getIncomplete() {
+ return incomplete;
+ }
+
+ @Override
+ protected int getPassed() {
+ return passed;
}
}
\ No newline at end of file
diff --git a/frontend/client/src/autotest/tko/TableView.java b/frontend/client/src/autotest/tko/TableView.java
index ae29e67..c3d04b0 100644
--- a/frontend/client/src/autotest/tko/TableView.java
+++ b/frontend/client/src/autotest/tko/TableView.java
@@ -43,14 +43,14 @@
import java.util.ListIterator;
import java.util.Map;
-public class TableView extends ConditionTabView
- implements DynamicTableListener, TableActionsWithExportCsvListener,
- ClickHandler, TableWidgetFactory, CommonPanelListener,
+public class TableView extends ConditionTabView
+ implements DynamicTableListener, TableActionsWithExportCsvListener,
+ ClickHandler, TableWidgetFactory, CommonPanelListener,
MultiListSelectPresenter.GeneratorHandler {
private static final int ROWS_PER_PAGE = 30;
private static final String COUNT_NAME = "Count in group";
private static final String STATUS_COUNTS_NAME = "Test pass rate";
- private static final String[] DEFAULT_COLUMNS =
+ private static final String[] DEFAULT_COLUMNS =
{"Test index", "Test name", "Job tag", "Hostname", "Status"};
private static final String[] TRIAGE_GROUP_COLUMNS =
{"Test name", "Status", COUNT_NAME, "Reason"};
@@ -87,14 +87,14 @@
return false;
}
}
-
+
private GroupCountField groupCountField =
new GroupCountField(COUNT_NAME, TestGroupDataSource.GROUP_COUNT_FIELD);
private GroupCountField statusCountsField =
new GroupCountField(STATUS_COUNTS_NAME, DataTable.WIDGET_COLUMN);
private TestSelectionListener listener;
-
+
private DynamicTable table;
private TableDecorator tableDecorator;
private SelectionManager selectionManager;
@@ -107,7 +107,7 @@
private DoubleListSelector columnSelectDisplay = new DoubleListSelector();
private CheckBox groupCheckbox = new CheckBox("Group by these columns and show counts");
- private CheckBox statusGroupCheckbox =
+ private CheckBox statusGroupCheckbox =
new CheckBox("Group by these columns and show pass rates");
private Button queryButton = new Button("Query");
private Panel tablePanel = new SimplePanel();
@@ -117,11 +117,11 @@
public enum TableViewConfig {
DEFAULT, PASS_RATE, TRIAGE
}
-
+
public static interface TableSwitchListener extends TestSelectionListener {
public void onSwitchToTable(TableViewConfig config);
}
-
+
public TableView(TestSelectionListener listener) {
this.listener = listener;
commonPanel.addListener(this);
@@ -147,17 +147,17 @@
queryButton.addClickHandler(this);
groupCheckbox.addClickHandler(this);
statusGroupCheckbox.addClickHandler(this);
-
+
Panel columnPanel = new VerticalPanel();
columnPanel.add(columnSelectDisplay);
columnPanel.add(groupCheckbox);
columnPanel.add(statusGroupCheckbox);
-
+
addWidget(columnPanel, "table_column_select");
addWidget(queryButton, "table_query_controls");
addWidget(tablePanel, "table_table");
}
-
+
private void selectColumnsByName(String[] columnNames) {
List<HeaderField> fields = new ArrayList<HeaderField>();
for (String name : columnNames) {
@@ -166,7 +166,7 @@
columnSelect.setSelectedItems(fields);
cleanupSortsForNewColumns();
}
-
+
public void setupDefaultView() {
tableSorts.clear();
selectColumnsByName(DEFAULT_COLUMNS);
@@ -198,14 +198,14 @@
table.addListener(this);
table.setWidgetFactory(this);
restoreTableSorting();
-
+
tableDecorator = new TableDecorator(table);
tableDecorator.addPaginators();
selectionManager = tableDecorator.addSelectionManager(false);
tableDecorator.addTableActionsWithExportCsvListener(this);
tablePanel.clear();
tablePanel.add(tableDecorator);
-
+
selectionManager = new SelectionManager(table, false);
}
@@ -234,7 +234,7 @@
} else {
groupDataSource = TestGroupDataSource.getStatusCountDataSource();
}
-
+
updateGroupColumns();
return groupDataSource;
}
@@ -243,7 +243,7 @@
commonPanel.updateStateFromView();
columnSelect.updateStateFromView();
}
-
+
private void updateViewFromState() {
commonPanel.updateViewFromState();
columnSelect.updateViewFromState();
@@ -263,7 +263,7 @@
private boolean isGroupField(HeaderField field) {
return field instanceof GroupCountField;
}
-
+
private void saveTableSorting() {
if (table != null) {
// we need our own copy so we can modify it later
@@ -272,7 +272,7 @@
}
private void restoreTableSorting() {
- for (ListIterator<SortSpec> i = tableSorts.listIterator(tableSorts.size());
+ for (ListIterator<SortSpec> i = tableSorts.listIterator(tableSorts.size());
i.hasPrevious();) {
SortSpec sortSpec = i.previous();
table.sortOnColumn(sortSpec.getField(), sortSpec.getDirection());
@@ -313,7 +313,7 @@
sqlConditionFilter.setAllParameters(condition);
table.refresh();
}
-
+
@Override
public void doQuery() {
if (savedColumns().isEmpty()) {
@@ -323,7 +323,7 @@
updateStateFromView();
refresh();
}
-
+
@Override
public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
Event event = Event.getCurrentEvent();
@@ -336,7 +336,7 @@
menu.showAtWindow(event.getClientX(), event.getClientY());
return;
}
-
+
if (isSelectEvent(event)) {
selectionManager.toggleSelected(row);
return;
@@ -353,7 +353,7 @@
private ContextMenu getContextMenu(final TestSet testSet) {
TestContextMenu menu = new TestContextMenu(testSet, listener);
-
+
if (!menu.addViewDetailsIfSingleTest() && isAnyGroupingEnabled()) {
menu.addItem("Drill down", new Command() {
public void execute() {
@@ -361,7 +361,7 @@
}
});
}
-
+
menu.addLabelItems();
return menu;
}
@@ -374,7 +374,7 @@
restoreHistoryState();
return historyToken;
}
-
+
private void doDrilldown(TestSet testSet) {
History.newItem(getDrilldownHistoryToken(testSet).toString());
}
@@ -395,7 +395,7 @@
}
return testSet;
}
-
+
private TestSet getTestSet(Collection<JSONObject> selectedObjects) {
CompositeTestSet compositeSet = new CompositeTestSet();
for (JSONObject row : selectedObjects) {
@@ -409,7 +409,7 @@
saveTableSorting();
updateHistory();
}
-
+
private void setCheckboxesEnabled() {
assert !(groupCheckbox.getValue() && statusGroupCheckbox.getValue());
@@ -485,7 +485,7 @@
protected void fillDefaultHistoryValues(Map<String, String> arguments) {
HeaderField defaultSortField = headerFields.getFieldByName(DEFAULT_COLUMNS[0]);
Utils.setDefaultValue(arguments, "sort", defaultSortField.getSqlName());
- Utils.setDefaultValue(arguments, "columns",
+ Utils.setDefaultValue(arguments, "columns",
Utils.joinStrings(",", Arrays.asList(DEFAULT_COLUMNS)));
}
@@ -515,7 +515,7 @@
private boolean isAnyGroupingEnabled() {
return getActiveGrouping() != GroupingType.NO_GROUPING;
}
-
+
private GroupingType getGroupingFromFields(List<HeaderField> fields) {
for (HeaderField field : fields) {
if (field.getName().equals(COUNT_NAME)) {
@@ -527,7 +527,7 @@
}
return GroupingType.NO_GROUPING;
}
-
+
/**
* Get grouping currently active for displayed table.
*/
@@ -540,8 +540,7 @@
StatusSummary statusSummary = StatusSummary.getStatusSummary(rowObject);
SimplePanel panel = new SimplePanel();
panel.add(new HTML(statusSummary.formatContents()));
- panel.getElement().getStyle().setProperty("backgroundColor",
- statusSummary.getColor());
+ panel.getElement().addClassName(statusSummary.getCssClass());
return panel;
}
@@ -563,10 +562,10 @@
public void onExportCsv() {
JSONObject extraParams = new JSONObject();
extraParams.put("columns", buildCsvColumnSpecs());
- TkoUtils.doCsvRequest((RpcDataSource) table.getDataSource(), table.getCurrentQuery(),
+ TkoUtils.doCsvRequest((RpcDataSource) table.getDataSource(), table.getCurrentQuery(),
extraParams);
}
-
+
private JSONArray buildCsvColumnSpecs() {
String[][] columnSpecs = buildColumnSpecs();
JSONArray jsonColumnSpecs = new JSONArray();
diff --git a/frontend/planner/rpc_interface.py b/frontend/planner/rpc_interface.py
index cc4eb89..c6b0e23 100644
--- a/frontend/planner/rpc_interface.py
+++ b/frontend/planner/rpc_interface.py
@@ -466,23 +466,23 @@
tests_run = []
machine = host.host.hostname
- status = host.status()
+ host_status = host.status()
bug_ids = set()
testruns = plan.testrun_set.filter(host=host, invalidated=False,
finalized=True)
for testrun in testruns:
test_name = testrun.tko_test.test
- success = (testrun.tko_test.status.word == 'GOOD')
+ test_status = testrun.tko_test.status.word
testrun_bug_ids = testrun.bugs.all().values_list(
'external_uid', flat=True)
tests_run.append({'test_name': test_name,
- 'success': success})
+ 'status': test_status})
bug_ids.update(testrun_bug_ids)
result.append({'machine': machine,
- 'status': status,
+ 'status': host_status,
'tests_run': tests_run,
'bug_ids': list(bug_ids)})
return result
diff --git a/frontend/planner/rpc_interface_unittest.py b/frontend/planner/rpc_interface_unittest.py
index 9dd345b..0048307 100644
--- a/frontend/planner/rpc_interface_unittest.py
+++ b/frontend/planner/rpc_interface_unittest.py
@@ -215,7 +215,7 @@
finalized=True)
host1_expected['tests_run'] = [{'test_name': 'test',
- 'success': False}]
+ 'status': self._running_status.word}]
actual = rpc_interface.get_machine_view_data(plan_id=self._plan.id)
self.assertEqual(sorted(actual), sorted(expected))
@@ -226,7 +226,7 @@
testrun.bugs.add(bug)
host1_expected['tests_run'] = [{'test_name': 'test',
- 'success': True}]
+ 'status': self._good_status.word}]
host1_expected['bug_ids'] = ['bug']
actual = rpc_interface.get_machine_view_data(plan_id=self._plan.id)
self.assertEqual(sorted(actual), sorted(expected))