Adding the GWT framework for the Test Planner.
This adds a new application to the GWT frontend, but does not link to it
from the other frontends yet. I don't anticipate anyone to be using this
just yet. Once the project reaches the point where I can release a working
prototype, I will create user documentation for it and send an announcement.
Signed-off-by: James Ren <jamesren@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@4378 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/client/src/autotest/common/DomUtils.java b/frontend/client/src/autotest/common/DomUtils.java
new file mode 100644
index 0000000..288a290
--- /dev/null
+++ b/frontend/client/src/autotest/common/DomUtils.java
@@ -0,0 +1,14 @@
+package autotest.common;
+
+import com.google.gwt.dom.client.Element;
+
+public class DomUtils {
+ public static void clearDomChildren(Element elem) {
+ Element child = elem.getFirstChildElement();
+ while (child != null) {
+ Element nextChild = child.getNextSiblingElement();
+ elem.removeChild(child);
+ child = nextChild;
+ }
+ }
+}
diff --git a/frontend/client/src/autotest/common/JsonRpcProxy.java b/frontend/client/src/autotest/common/JsonRpcProxy.java
index a148adf..4d16b7c 100644
--- a/frontend/client/src/autotest/common/JsonRpcProxy.java
+++ b/frontend/client/src/autotest/common/JsonRpcProxy.java
@@ -16,6 +16,7 @@
public abstract class JsonRpcProxy {
public static final String AFE_BASE_URL = "/afe/server/";
public static final String TKO_BASE_URL = "/new_tko/server/";
+ public static final String PLANNER_BASE_URL = "/planner/server/";
private static final String RPC_URL_SUFFIX = "rpc/";
private static final String JSON_RPC_URL_SUFFIX = "jsonp_rpc/";
diff --git a/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java b/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java
new file mode 100644
index 0000000..822ac22
--- /dev/null
+++ b/frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java
@@ -0,0 +1,589 @@
+package autotest.common.spreadsheet;
+
+import autotest.common.UnmodifiableSublistView;
+import autotest.common.Utils;
+import autotest.common.table.FragmentedTable;
+import autotest.common.table.TableRenderer;
+import autotest.common.ui.RightClickTable;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.IncrementalCommand;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HTMLTable;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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;
+ private static final String BLANK_STRING = "(empty)";
+ private static final int CELL_PADDING_PX = 2;
+ 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>();
+ private Map<Header, Integer> rowHeaderMap = new HashMap<Header, Integer>();
+ private Map<Header, Integer> columnHeaderMap = new HashMap<Header, Integer>();
+ protected CellInfo[][] dataCells, rowHeaderCells, columnHeaderCells;
+ private RightClickTable rowHeaders = new RightClickTable();
+ private RightClickTable columnHeaders = new RightClickTable();
+ private FlexTable parentTable = new FlexTable();
+ private FragmentedTable dataTable = new FragmentedTable();
+ private int rowsPerIteration;
+ 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() {
+ }
+
+ public HeaderImpl(Collection<? extends String> arg0) {
+ super(arg0);
+ }
+
+ public static Header fromBaseType(List<String> baseType) {
+ return new HeaderImpl(baseType);
+ }
+ }
+
+ public static class CellInfo {
+ public Header row, column;
+ public String contents;
+ public String color;
+ 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,
+ rowIndex, rowsPerIteration, true);
+ rowIndex += rowsPerIteration;
+ if (rowIndex > dataCells.length) {
+ state++;
+ }
+ }
+
+ public boolean execute() {
+ switch (state) {
+ case 0:
+ computeRowsPerIteration();
+ computeHeaderCells();
+ break;
+ case 1:
+ renderHeaders();
+ expandRowHeaders();
+ break;
+ case 2:
+ // resize everything to the max dimensions (the window size)
+ fillWindow(false);
+ break;
+ case 3:
+ // set main table to match header sizes
+ matchRowHeights(rowHeaders, dataCells);
+ matchColumnWidths(columnHeaders, dataCells);
+ dataTable.setVisible(false);
+ break;
+ case 4:
+ // render the main data table
+ renderSomeRows();
+ return true;
+ case 5:
+ dataTable.updateBodyElems();
+ dataTable.setVisible(true);
+ break;
+ case 6:
+ // now expand headers as necessary
+ // this can be very slow, so put it in it's own cycle
+ matchRowHeights(dataTable, rowHeaderCells);
+ break;
+ case 7:
+ matchColumnWidths(dataTable, columnHeaderCells);
+ renderHeaders();
+ break;
+ case 8:
+ // shrink the scroller if the table ended up smaller than the window
+ fillWindow(true);
+ DeferredCommand.addCommand(onFinished);
+ return false;
+ }
+
+ state++;
+ return true;
+ }
+ }
+
+ 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);
+ }
+
+ private void setupTableInput(RightClickTable table) {
+ table.addContextMenuHandler(this);
+ table.addClickHandler(this);
+ }
+
+ protected void killPaddingAndSpacing(HTMLTable table) {
+ table.setCellSpacing(0);
+ table.setCellPadding(0);
+ }
+
+ /*
+ * Wrap a widget with a panel that will clip its contents rather than grow
+ * too much.
+ */
+ protected Panel wrapWithClipper(Widget w) {
+ SimplePanel wrapper = new SimplePanel();
+ wrapper.add(w);
+ 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);
+ assert !headerMap.containsKey(headerObject);
+ 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
+ */
+ public void prepareForData() {
+ dataCells = new CellInfo[rowHeaderValues.size()][columnHeaderValues.size()];
+ }
+
+ public CellInfo getCellInfo(int row, int column) {
+ Header rowHeader = rowHeaderValues.get(row);
+ Header columnHeader = columnHeaderValues.get(column);
+ if (dataCells[row][column] == null) {
+ dataCells[row][column] = new CellInfo(rowHeader, columnHeader, "");
+ }
+ 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.
+ */
+ public void render(IncrementalCommand onFinished) {
+ DeferredCommand.addCommand(new RenderCommand(onFinished));
+ }
+
+ private void renderHeaders() {
+ 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,
+ boolean isRows) {
+ int headerSize = fields.size();
+ String[] lastFieldValue = new String[headerSize];
+ CellInfo[] lastCellInfo = new CellInfo[headerSize];
+ int[] counter = new int[headerSize];
+ boolean newHeader;
+ for (int headerIndex = 0; headerIndex < headerValues.size(); headerIndex++) {
+ Header header = headerValues.get(headerIndex);
+ newHeader = false;
+ for (int fieldIndex = 0; fieldIndex < headerSize; fieldIndex++) {
+ String fieldValue = header.get(fieldIndex);
+ if (newHeader || !fieldValue.equals(lastFieldValue[fieldIndex])) {
+ newHeader = true;
+ Header currentHeader = getSubHeader(header, fieldIndex + 1);
+ String cellContents = formatHeader(fields.get(fieldIndex), fieldValue);
+ CellInfo cellInfo;
+ if (isRows) {
+ cellInfo = new CellInfo(currentHeader, null, cellContents);
+ cells[headerIndex][fieldIndex] = cellInfo;
+ } else {
+ cellInfo = new CellInfo(null, currentHeader, cellContents);
+ cells[fieldIndex][counter[fieldIndex]] = cellInfo;
+ counter[fieldIndex]++;
+ }
+ lastFieldValue[fieldIndex] = fieldValue;
+ lastCellInfo[fieldIndex] = cellInfo;
+ } else {
+ incrementSpan(lastCellInfo[fieldIndex], isRows);
+ }
+ }
+ }
+ }
+
+ private String formatHeader(String field, String value) {
+ if (value.equals("")) {
+ return BLANK_STRING;
+ }
+ value = Utils.escape(value);
+ if (field.equals("kernel")) {
+ // line break after each /, for long paths
+ value = value.replace("/", "/<br>").replace("/<br>/<br>", "//");
+ }
+ return value;
+ }
+
+ private void incrementSpan(CellInfo cellInfo, boolean isRows) {
+ if (isRows) {
+ cellInfo.rowSpan++;
+ } else {
+ cellInfo.colSpan++;
+ }
+ }
+
+ private Header getSubHeader(Header header, int length) {
+ if (length == header.size()) {
+ return header;
+ }
+ List<String> subHeader = new UnmodifiableSublistView<String>(header, 0, length);
+ return new HeaderImpl(subHeader);
+ }
+
+ private void matchRowHeights(HTMLTable from, CellInfo[][] to) {
+ int lastColumn = to[0].length - 1;
+ int rowCount = from.getRowCount();
+ for (int row = 0; row < rowCount; row++) {
+ int height = getRowHeight(from, row);
+ 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;
+ for (int column = 0; column < from.getCellCount(lastFromRow); column++) {
+ int width = getColumnWidth(from, column);
+ 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();
+ if (div == null)
+ return null;
+ String contents = Utils.unescape(div.getInnerHTML());
+ if (contents.equals(BLANK_STRING))
+ contents = "";
+ return contents;
+ }
+
+ public void clear() {
+ rowHeaderValues.clear();
+ columnHeaderValues.clear();
+ rowHeaderMap.clear();
+ 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() +
+ columnHeaders.getOffsetHeight());
+ newHeightPx = adjustMaxDimension(newHeightPx);
+ 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));
+ scrollPanel.setSize(getSizePxString(newWidthPx + SCROLLBAR_FUDGE),
+ getSizePxString(newHeightPx + SCROLLBAR_FUDGE));
+ }
+
+ /**
+ * 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,
+ 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.
+ */
+ protected void expandRowHeaders() {
+ 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;
+ if (cellInfo.row == null) {
+ tdElement = getCellElement(columnHeaders, 0, getColumnPosition(cellInfo.column));
+ } else if (cellInfo.column == null) {
+ tdElement = getCellElement(rowHeaders, getRowPosition(cellInfo.row), 0);
+ } else {
+ 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() -
+ 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() -
+ TD_BORDER_PX;
+ }
+
+ /**
+ * Update floating headers.
+ */
+ @Override
+ public void onScroll(ScrollEvent event) {
+ int scrollLeft = scrollPanel.getHorizontalScrollPosition();
+ int scrollTop = scrollPanel.getScrollPosition();
+
+ setColumnHeadersOffset(-scrollLeft);
+ setRowHeadersOffset(-scrollTop);
+ }
+
+ protected void setRowHeadersOffset(int offset) {
+ rowHeaders.getElement().getStyle().setPropertyPx("top", offset);
+ }
+
+ 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;
+ column = adjustRowHeaderColumnIndex(row, column);
+ }
+ else if (event.getSource() == columnHeaders) {
+ cells = columnHeaderCells;
+ }
+ else {
+ assert event.getSource() == dataTable;
+ cells = dataCells;
+ }
+ 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
+ * 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.
+ */
+ private int adjustRowHeaderColumnIndex(int row, int column) {
+ for (int i = 0; i < rowFields.size(); i++) {
+ if (rowHeaderCells[row][i] != null) {
+ return i + column;
+ }
+ }
+
+ throw new RuntimeException("Failed to find non-null cell");
+ }
+
+ public void setListener(SpreadsheetListener listener) {
+ this.listener = listener;
+ }
+
+ public void setHighlighted(CellInfo cell, boolean highlighted) {
+ Element cellElement = getCellElement(cell);
+ if (highlighted) {
+ cellElement.setClassName(HIGHLIGHTED_CLASS);
+ } else {
+ cellElement.setClassName("");
+ }
+ }
+
+ public List<Integer> getAllTestIndices() {
+ List<Integer> testIndices = new ArrayList<Integer>();
+
+ for (CellInfo[] row : dataCells) {
+ for (CellInfo cellInfo : row) {
+ if (cellInfo != null && !cellInfo.isEmpty()) {
+ testIndices.add(cellInfo.testIndex);
+ }
+ }
+ }
+
+ return testIndices;
+ }
+}
diff --git a/frontend/client/src/autotest/common/spreadsheet/SpreadsheetSelectionManager.java b/frontend/client/src/autotest/common/spreadsheet/SpreadsheetSelectionManager.java
new file mode 100644
index 0000000..2dd5c2f
--- /dev/null
+++ b/frontend/client/src/autotest/common/spreadsheet/SpreadsheetSelectionManager.java
@@ -0,0 +1,91 @@
+package autotest.common.spreadsheet;
+
+import autotest.common.Utils;
+import autotest.common.spreadsheet.Spreadsheet.CellInfo;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+// TODO: hopefully some of this could be combined with autotest.common.table.SelectionManager using
+// generics
+// TODO: get rid of header selection
+public class SpreadsheetSelectionManager {
+ private Spreadsheet spreadsheet;
+ private Collection<CellInfo> selectedCells = new HashSet<CellInfo>();
+ private SpreadsheetSelectionListener listener;
+
+ public static interface SpreadsheetSelectionListener {
+ public void onCellsSelected(List<CellInfo> cells);
+ public void onCellsDeselected(List<CellInfo> cells);
+ }
+
+ public SpreadsheetSelectionManager(Spreadsheet spreadsheet,
+ SpreadsheetSelectionListener listener) {
+ this.spreadsheet = spreadsheet;
+ this.listener = listener;
+ }
+
+ public void toggleSelected(CellInfo cell) {
+ if (selectedCells.contains(cell)) {
+ deselectCell(cell);
+ notifyDeselected(Utils.wrapObjectWithList(cell));
+ } else {
+ selectCell(cell);
+ notifySelected(Utils.wrapObjectWithList(cell));
+ }
+ }
+
+ private void selectCell(CellInfo cell) {
+ selectedCells.add(cell);
+ spreadsheet.setHighlighted(cell, true);
+ }
+
+ private void deselectCell(CellInfo cell) {
+ selectedCells.remove(cell);
+ spreadsheet.setHighlighted(cell, false);
+ }
+
+ public List<CellInfo> getSelectedCells() {
+ return new ArrayList<CellInfo>(selectedCells);
+ }
+
+ public boolean isEmpty() {
+ return selectedCells.isEmpty();
+ }
+
+ public void clearSelection() {
+ List<CellInfo> cells = getSelectedCells();
+ for (CellInfo cell : cells) {
+ deselectCell(cell);
+ }
+ notifyDeselected(cells);
+ }
+
+ public void selectAll() {
+ List<CellInfo> selectedCells = new ArrayList<CellInfo>();
+ for (CellInfo[] row : spreadsheet.dataCells) {
+ for (CellInfo cell : row) {
+ if (cell == null || cell.isEmpty()) {
+ continue;
+ }
+ selectCell(cell);
+ selectedCells.add(cell);
+ }
+ }
+ notifySelected(selectedCells);
+ }
+
+ private void notifyDeselected(List<CellInfo> cells) {
+ if (listener != null) {
+ listener.onCellsDeselected(cells);
+ }
+ }
+
+ private void notifySelected(List<CellInfo> selectedCells) {
+ if (listener != null) {
+ listener.onCellsSelected(selectedCells);
+ }
+ }
+}
diff --git a/frontend/client/src/autotest/common/table/FragmentedTable.java b/frontend/client/src/autotest/common/table/FragmentedTable.java
new file mode 100644
index 0000000..53a30b3
--- /dev/null
+++ b/frontend/client/src/autotest/common/table/FragmentedTable.java
@@ -0,0 +1,148 @@
+package autotest.common.table;
+
+import autotest.common.DomUtils;
+import autotest.common.ui.RightClickTable;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTMLTable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Customized table class supporting multiple tbody elements. It is modified to support input
+ * handling, getRowCount(), getCellCount(), and getCellFormatter().getElement(). getElement()
+ * also works. Calls to other methods aren't guaranteed to work.
+ */
+public class FragmentedTable extends RightClickTable {
+ public class FragmentedCellFormatter extends HTMLTable.CellFormatter {
+ @Override
+ public Element getElement(int row, int column) {
+ checkCellBounds(row, column);
+ Element bodyElem = bodyElems.get(getFragmentIndex(row));
+ return getCellElement(bodyElem, getRowWithinFragment(row), column);
+ }
+
+ /**
+ * Native method to efficiently get a td element from a tbody. Copied from GWT's
+ * HTMLTable.java.
+ */
+ private native Element getCellElement(Element tbody, int row, int col) /*-{
+ return tbody.rows[row].cells[col];
+ }-*/;
+ }
+
+ private List<Element> bodyElems = new ArrayList<Element>();
+ private int totalRowCount;
+ private int rowsPerFragment;
+
+ public FragmentedTable() {
+ super();
+ setCellFormatter(new FragmentedCellFormatter());
+
+ // Reset the FragmentedTable to clear out elements that were added by the HTMLTable and
+ // FlexTable constructors
+ reset();
+ }
+
+ /**
+ * This method must be called after added or removing tbody elements and before using other
+ * functionality (accessing cell elements, input handling, etc.).
+ */
+ public void updateBodyElems() {
+ totalRowCount = 0;
+ Element tbody = DOM.getFirstChild(getElement());
+ for(; tbody != null; tbody = DOM.getNextSibling(tbody)) {
+ assert tbody.getTagName().equalsIgnoreCase("tbody");
+ bodyElems.add(tbody);
+ totalRowCount += getRowCount(tbody);
+ }
+ }
+
+ public void reset() {
+ bodyElems.clear();
+ DomUtils.clearDomChildren(getElement());
+ }
+
+ private int getRowWithinFragment(int row) {
+ return row % rowsPerFragment;
+ }
+
+ private int getFragmentIndex(int row) {
+ return row / rowsPerFragment;
+ }
+
+ @Override
+ public HTMLTable.Cell getCellForEvent(ClickEvent event) {
+ return getCellForDomEvent(event);
+ }
+
+ @Override
+ protected RowColumn getCellPosition(Element td) {
+ Element tr = DOM.getParent(td);
+ Element body = DOM.getParent(tr);
+ int fragmentIndex = DOM.getChildIndex(getElement(), body);
+ int rowWithinFragment = DOM.getChildIndex(body, tr);
+ int row = fragmentIndex * rowsPerFragment + rowWithinFragment;
+ int column = DOM.getChildIndex(tr, td);
+ return new RowColumn(row, column);
+ }
+
+ /**
+ * This is a modified version of getEventTargetCell() from HTMLTable.java.
+ */
+ @Override
+ protected Element getEventTargetCell(Event event) {
+ Element td = DOM.eventGetTarget(event);
+ for (; td != null; td = DOM.getParent(td)) {
+ // If it's a TD, it might be the one we're looking for.
+ if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
+ // Make sure it's directly a part of this table before returning
+ // it.
+ Element tr = DOM.getParent(td);
+ Element body = DOM.getParent(tr);
+ Element tableElem = DOM.getParent(body);
+ if (tableElem == getElement()) {
+ return td;
+ }
+ }
+ // If we run into this table's element, we're out of options.
+ if (td == getElement()) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int getCellCount(int row) {
+ Element bodyElem = bodyElems.get(getFragmentIndex(row));
+ return getCellCount(bodyElem, getRowWithinFragment(row));
+ }
+
+ @Override
+ public int getRowCount() {
+ return totalRowCount;
+ }
+
+ private native int getRowCount(Element tbody) /*-{
+ return tbody.rows.length;
+ }-*/;
+
+ private native int getCellCount(Element tbody, int row) /*-{
+ return tbody.rows[row].cells.length;
+ }-*/;
+
+ /**
+ * This must be called before using other functionality (accessing cell elements, input
+ * handling, etc.).
+ * @param rowsPerFragment The number of rows in each tbody. The last tbody may have fewer
+ * rows. All others must have exactly this number of rows.
+ */
+ public void setRowsPerFragment(int rowsPerFragment) {
+ this.rowsPerFragment = rowsPerFragment;
+ }
+}
diff --git a/frontend/client/src/autotest/common/table/TableRenderer.java b/frontend/client/src/autotest/common/table/TableRenderer.java
new file mode 100644
index 0000000..1641dfb
--- /dev/null
+++ b/frontend/client/src/autotest/common/table/TableRenderer.java
@@ -0,0 +1,102 @@
+package autotest.common.table;
+
+import autotest.common.DomUtils;
+import autotest.common.spreadsheet.Spreadsheet.CellInfo;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.HTMLTable;
+
+
+public class TableRenderer {
+ // 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,
+ int startRow, int maxRows, boolean renderNull) {
+ StringBuffer htmlBuffer= new StringBuffer();
+ htmlBuffer.append("<table><tbody>");
+ for (int rowIndex = startRow; rowIndex < startRow + maxRows && rowIndex < rows.length;
+ rowIndex++) {
+ CellInfo[] row = rows[rowIndex];
+ htmlBuffer.append("<tr>");
+ for (CellInfo cell : row) {
+ if (cell == null && renderNull) {
+ 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.rowSpan > 1) {
+ tdAttributes += attributeString("rowspan", Integer.toString(cell.rowSpan));
+ }
+ if (cell.colSpan > 1) {
+ tdAttributes += attributeString("colspan", Integer.toString(cell.colSpan));
+ }
+
+ if (cell.widthPx != null) {
+ divStyle += SIZE_PREFIX + "width: " + cell.widthPx + "px; ";
+ }
+ if (cell.heightPx != null) {
+ divStyle += SIZE_PREFIX + "height: " + cell.heightPx + "px; ";
+ }
+ if (!divStyle.equals("")) {
+ divAttributes += attributeString("style", divStyle);
+ }
+ if (cell.isEmpty()) {
+ divAttributes += attributeString("class", NONCLICKABLE_CLASS);
+ }
+
+ htmlBuffer.append("<td " + tdAttributes + ">");
+ htmlBuffer.append("<div " + divAttributes + ">");
+ htmlBuffer.append(cell.contents);
+ htmlBuffer.append("</div></td>");
+ }
+ }
+ 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);
+ }
+
+ private void renderBody(HTMLTable tableObject, String html) {
+ // 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.
+ */
+ protected native void setBodyElement(HTMLTable table, Element newBody) /*-{
+ table.@com.google.gwt.user.client.ui.HTMLTable::bodyElem = newBody;
+ }-*/;
+
+}
diff --git a/frontend/client/src/autotest/planner/AutoprocessedTab.java b/frontend/client/src/autotest/planner/AutoprocessedTab.java
new file mode 100644
index 0000000..76d2a51
--- /dev/null
+++ b/frontend/client/src/autotest/planner/AutoprocessedTab.java
@@ -0,0 +1,18 @@
+package autotest.planner;
+
+
+public class AutoprocessedTab {
+
+ public static interface Display {
+ // Not yet implemented
+ }
+
+
+ @SuppressWarnings("unused")
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/AutoprocessedTabDisplay.java b/frontend/client/src/autotest/planner/AutoprocessedTabDisplay.java
new file mode 100644
index 0000000..f3c61d9
--- /dev/null
+++ b/frontend/client/src/autotest/planner/AutoprocessedTabDisplay.java
@@ -0,0 +1,13 @@
+package autotest.planner;
+
+import autotest.common.ui.TabView;
+
+
+public class AutoprocessedTabDisplay extends TabView implements AutoprocessedTab.Display {
+
+ @Override
+ public String getElementId() {
+ return "autoprocessed";
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/HistoryTab.java b/frontend/client/src/autotest/planner/HistoryTab.java
new file mode 100644
index 0000000..a476359
--- /dev/null
+++ b/frontend/client/src/autotest/planner/HistoryTab.java
@@ -0,0 +1,18 @@
+package autotest.planner;
+
+
+public class HistoryTab {
+
+ public static interface Display {
+ // Not yet implemented
+ }
+
+
+ @SuppressWarnings("unused")
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/HistoryTabDisplay.java b/frontend/client/src/autotest/planner/HistoryTabDisplay.java
new file mode 100644
index 0000000..588804d
--- /dev/null
+++ b/frontend/client/src/autotest/planner/HistoryTabDisplay.java
@@ -0,0 +1,13 @@
+package autotest.planner;
+
+import autotest.common.ui.TabView;
+
+
+public class HistoryTabDisplay extends TabView implements HistoryTab.Display {
+
+ @Override
+ public String getElementId() {
+ return "history";
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/MachineViewTab.java b/frontend/client/src/autotest/planner/MachineViewTab.java
new file mode 100644
index 0000000..c0da926
--- /dev/null
+++ b/frontend/client/src/autotest/planner/MachineViewTab.java
@@ -0,0 +1,17 @@
+package autotest.planner;
+
+
+public class MachineViewTab {
+
+ public static interface Display {
+ // Not yet implemented
+ }
+
+ @SuppressWarnings("unused")
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/MachineViewTabDisplay.java b/frontend/client/src/autotest/planner/MachineViewTabDisplay.java
new file mode 100644
index 0000000..b542891
--- /dev/null
+++ b/frontend/client/src/autotest/planner/MachineViewTabDisplay.java
@@ -0,0 +1,13 @@
+package autotest.planner;
+
+import autotest.common.ui.TabView;
+
+
+public class MachineViewTabDisplay extends TabView implements MachineViewTab.Display {
+
+ @Override
+ public String getElementId() {
+ return "machine_view";
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/OverviewTab.java b/frontend/client/src/autotest/planner/OverviewTab.java
new file mode 100644
index 0000000..a8054f0
--- /dev/null
+++ b/frontend/client/src/autotest/planner/OverviewTab.java
@@ -0,0 +1,17 @@
+package autotest.planner;
+
+
+public class OverviewTab {
+
+ public static interface Display {
+ // Not yet implemented
+ }
+
+ @SuppressWarnings("unused")
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/OverviewTabDisplay.java b/frontend/client/src/autotest/planner/OverviewTabDisplay.java
new file mode 100644
index 0000000..ac2841c
--- /dev/null
+++ b/frontend/client/src/autotest/planner/OverviewTabDisplay.java
@@ -0,0 +1,13 @@
+package autotest.planner;
+
+import autotest.common.ui.TabView;
+
+
+public class OverviewTabDisplay extends TabView implements OverviewTab.Display {
+
+ @Override
+ public String getElementId() {
+ return "overview";
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/TestPlanSelector.java b/frontend/client/src/autotest/planner/TestPlanSelector.java
new file mode 100644
index 0000000..6eabc5e
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestPlanSelector.java
@@ -0,0 +1,49 @@
+package autotest.planner;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.ui.HasText;
+
+public class TestPlanSelector implements ClickHandler, KeyPressHandler {
+
+ public static interface Display {
+ public HasText getInputText();
+ public HasClickHandlers getShowButton();
+ public HasKeyPressHandlers getInputField();
+ }
+
+
+ private Display display;
+ private String selectedPlan;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ display.getShowButton().addClickHandler(this);
+ display.getInputField().addKeyPressHandler(this);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ selectPlan();
+ }
+
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getCharCode() == KeyCodes.KEY_ENTER) {
+ selectPlan();
+ }
+ }
+
+ private void selectPlan() {
+ selectedPlan = display.getInputText().getText();
+ }
+
+ public String getSelectedPlan() {
+ return selectedPlan;
+ }
+}
diff --git a/frontend/client/src/autotest/planner/TestPlanSelectorDisplay.java b/frontend/client/src/autotest/planner/TestPlanSelectorDisplay.java
new file mode 100644
index 0000000..3da5527
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestPlanSelectorDisplay.java
@@ -0,0 +1,41 @@
+package autotest.planner;
+
+import autotest.common.Utils;
+
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.TextBox;
+
+public class TestPlanSelectorDisplay extends Composite implements TestPlanSelector.Display {
+
+ private TextBox inputField;
+ private Button show;
+
+ public void initialize() {
+ HTMLPanel panel = Utils.divToPanel("test_plan_selector");
+
+ inputField = new TextBox();
+ panel.add(inputField, "test_plan_selector_input");
+
+ show = new Button("show");
+ panel.add(show, "test_plan_selector_button");
+
+ initWidget(panel);
+ }
+
+ public HasText getInputText() {
+ return inputField;
+ }
+
+ public HasKeyPressHandlers getInputField() {
+ return inputField;
+ }
+
+ public HasClickHandlers getShowButton() {
+ return show;
+ }
+}
diff --git a/frontend/client/src/autotest/planner/TestPlannerClient.java b/frontend/client/src/autotest/planner/TestPlannerClient.java
new file mode 100644
index 0000000..51e078c
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestPlannerClient.java
@@ -0,0 +1,77 @@
+package autotest.planner;
+
+import autotest.common.CustomHistory;
+import autotest.common.JsonRpcProxy;
+import autotest.common.SiteCommonClassFactory;
+import autotest.common.StaticDataRepository;
+import autotest.common.ui.CustomTabPanel;
+import autotest.common.ui.NotifyManager;
+import autotest.planner.triage.TriageViewTab;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.ui.RootPanel;
+
+public class TestPlannerClient implements EntryPoint {
+
+ private TestPlanSelector planSelector = new TestPlanSelector();
+ private TestPlanSelectorDisplay planSelectorView = new TestPlanSelectorDisplay();
+
+ private OverviewTab overviewTab = new OverviewTab();
+ private OverviewTabDisplay overviewTabDisplay = new OverviewTabDisplay();
+
+ private MachineViewTab machineViewTab = new MachineViewTab();
+ private MachineViewTabDisplay machineViewTabDisplay = new MachineViewTabDisplay();
+
+ private TestViewTab testViewTab = new TestViewTab();
+ private TestViewTabDisplay testViewTabDisplay = new TestViewTabDisplay();
+
+ private TriageViewTab triageViewTab = new TriageViewTab(planSelector);
+
+ private AutoprocessedTab autoprocessedTab = new AutoprocessedTab();
+ private AutoprocessedTabDisplay autoprocessedTabDisplay = new AutoprocessedTabDisplay();
+
+ private HistoryTab historyTab = new HistoryTab();
+ private HistoryTabDisplay historyTabDisplay = new HistoryTabDisplay();
+
+ private CustomTabPanel mainTabPanel = new CustomTabPanel();
+
+ public void onModuleLoad() {
+ JsonRpcProxy.setDefaultBaseUrl(JsonRpcProxy.PLANNER_BASE_URL);
+
+ NotifyManager.getInstance().initialize();
+
+ StaticDataRepository.getRepository().refresh(
+ new StaticDataRepository.FinishedCallback() {
+ public void onFinished() {
+ finishLoading();
+ }
+ });
+ }
+
+ private void finishLoading() {
+ SiteCommonClassFactory.globalInitialize();
+
+ overviewTab.bindDisplay(overviewTabDisplay);
+ machineViewTab.bindDisplay(machineViewTabDisplay);
+ testViewTab.bindDisplay(testViewTabDisplay);
+ autoprocessedTab.bindDisplay(autoprocessedTabDisplay);
+ historyTab.bindDisplay(historyTabDisplay);
+
+ planSelectorView.initialize();
+ planSelector.bindDisplay(planSelectorView);
+ mainTabPanel.getCommonAreaPanel().add(planSelectorView);
+
+ mainTabPanel.addTabView(overviewTabDisplay);
+ mainTabPanel.addTabView(machineViewTabDisplay);
+ mainTabPanel.addTabView(testViewTabDisplay);
+ mainTabPanel.addTabView(triageViewTab);
+ mainTabPanel.addTabView(autoprocessedTabDisplay);
+ mainTabPanel.addTabView(historyTabDisplay);
+
+ final RootPanel tabsRoot = RootPanel.get("tabs");
+ tabsRoot.add(mainTabPanel);
+ CustomHistory.processInitialToken();
+ mainTabPanel.initialize();
+ tabsRoot.removeStyleName("hidden");
+ }
+}
diff --git a/frontend/client/src/autotest/planner/TestViewTab.java b/frontend/client/src/autotest/planner/TestViewTab.java
new file mode 100644
index 0000000..0afcb37
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestViewTab.java
@@ -0,0 +1,17 @@
+package autotest.planner;
+
+
+public class TestViewTab {
+
+ public static interface Display {
+ // Not yet implemented
+ }
+
+ @SuppressWarnings("unused")
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/TestViewTabDisplay.java b/frontend/client/src/autotest/planner/TestViewTabDisplay.java
new file mode 100644
index 0000000..15f9d83
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestViewTabDisplay.java
@@ -0,0 +1,13 @@
+package autotest.planner;
+
+import autotest.common.ui.TabView;
+
+
+public class TestViewTabDisplay extends TabView implements TestViewTab.Display {
+
+ @Override
+ public String getElementId() {
+ return "test_view";
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/triage/FailureTable.java b/frontend/client/src/autotest/planner/triage/FailureTable.java
new file mode 100644
index 0000000..d39e1d1
--- /dev/null
+++ b/frontend/client/src/autotest/planner/triage/FailureTable.java
@@ -0,0 +1,142 @@
+package autotest.planner.triage;
+
+import autotest.common.spreadsheet.Spreadsheet;
+import autotest.common.spreadsheet.Spreadsheet.CellInfo;
+import autotest.common.spreadsheet.Spreadsheet.Header;
+import autotest.common.spreadsheet.Spreadsheet.HeaderImpl;
+import autotest.common.spreadsheet.Spreadsheet.SpreadsheetListener;
+
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.user.client.IncrementalCommand;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+class FailureTable implements SpreadsheetListener {
+
+ public interface Display {
+ public Spreadsheet getSpreadsheet();
+ }
+
+ private static class Failure {
+ int id;
+ String machine;
+ boolean blocked;
+ String testName;
+ String reason;
+ boolean seen;
+
+ private Failure(int id, String machine, boolean blocked,
+ String testName, String reason, boolean seen) {
+ this.id = id;
+ this.machine = machine;
+ this.blocked = blocked;
+ this.testName = testName;
+ this.reason = reason;
+ this.seen = seen;
+ }
+
+ public static Failure fromJsonObject(JSONObject failureObj) {
+ return new Failure((int) failureObj.get("id").isNumber().doubleValue(),
+ failureObj.get("machine").isString().stringValue(),
+ failureObj.get("blocked").isBoolean().booleanValue(),
+ failureObj.get("test_name").isString().stringValue(),
+ failureObj.get("reason").isString().stringValue(),
+ failureObj.get("seen").isBoolean().booleanValue());
+ }
+ }
+
+ private Display display;
+ private String group;
+ private LinkedList<Failure> failures = new LinkedList<Failure>();
+ private boolean rendered;
+
+ public FailureTable(String group) {
+ this.group = group;
+ }
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+ public void addFailure(JSONObject failureObj) {
+ rendered = false;
+
+ Failure failure = Failure.fromJsonObject(failureObj);
+
+ if (failure.seen) {
+ failures.addLast(failure);
+ } else {
+ failures.addFirst(failure);
+ }
+ }
+
+ public Display getDisplay() {
+ if (!rendered) {
+ renderDisplay();
+ }
+
+ return display;
+ }
+
+ public void renderDisplay() {
+ Spreadsheet spreadsheet = display.getSpreadsheet();
+
+ Header rowFields = HeaderImpl.fromBaseType(Collections.singletonList("machine"));
+ Header columnFields = new HeaderImpl();
+ columnFields.add("group");
+ columnFields.add("failure");
+ spreadsheet.setHeaderFields(rowFields, columnFields);
+
+ for (int i = 0; i < failures.size(); i++) {
+ Failure failure = failures.get(i);
+ String machine = (i+1) + ": " + failure.machine;
+ if (failure.blocked) {
+ machine += " (blocked)";
+ }
+ spreadsheet.addRowHeader(Collections.singletonList(machine));
+ }
+ spreadsheet.addColumnHeader(createHeaderGroup("Test"));
+ spreadsheet.addColumnHeader(createHeaderGroup("Reason"));
+
+ spreadsheet.prepareForData();
+
+ for (int row = 0; row < failures.size(); row++) {
+ CellInfo test = spreadsheet.getCellInfo(row, 0);
+ CellInfo reason = spreadsheet.getCellInfo(row, 1);
+ Failure failure = failures.get(row);
+
+ test.contents = failure.testName;
+ reason.contents = failure.reason;
+
+ if (!failure.seen) {
+ test.contents = "<b>" + test.contents + "</b>";
+ reason.contents = "<b>" + reason.contents + "</b>";
+ }
+ }
+
+ spreadsheet.setVisible(true);
+
+ spreadsheet.render(new IncrementalCommand() {
+ @Override
+ public boolean execute() {
+ rendered = true;
+ return false;
+ }
+ });
+ }
+
+ private List<String> createHeaderGroup(String label) {
+ List<String> header = new ArrayList<String>();
+ header.add(group);
+ header.add(label);
+ return header;
+ }
+
+ @Override
+ public void onCellClicked(CellInfo cellInfo, boolean isRightClick) {
+ //TODO: handle row clicks (pop up the triage panel)
+ }
+}
diff --git a/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java b/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java
new file mode 100644
index 0000000..6b91267
--- /dev/null
+++ b/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java
@@ -0,0 +1,18 @@
+package autotest.planner.triage;
+
+import autotest.common.spreadsheet.Spreadsheet;
+
+import com.google.gwt.user.client.ui.Composite;
+
+public class FailureTableDisplay extends Composite implements FailureTable.Display {
+
+ private Spreadsheet spreadsheet = new Spreadsheet();
+
+ public FailureTableDisplay() {
+ initWidget(spreadsheet);
+ }
+
+ public Spreadsheet getSpreadsheet() {
+ return spreadsheet;
+ }
+}
diff --git a/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java b/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java
new file mode 100644
index 0000000..f8b2276
--- /dev/null
+++ b/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java
@@ -0,0 +1,35 @@
+package autotest.planner.triage;
+
+import autotest.common.ui.NotifyManager;
+
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+
+public class TriageViewDisplay implements TriageViewPresenter.Display {
+
+ private Panel container = new VerticalPanel();
+
+ public void initialize(HTMLPanel htmlPanel) {
+ htmlPanel.add(container, "triage_failure_tables");
+ }
+
+ @Override
+ public void setLoading(boolean loading) {
+ NotifyManager.getInstance().setLoading(loading);
+ container.setVisible(!loading);
+ }
+
+ @Override
+ public FailureTable.Display generateFailureTable() {
+ FailureTableDisplay display = new FailureTableDisplay();
+ container.add(display);
+ return display;
+ }
+
+ @Override
+ public void clearAllFailureTables() {
+ container.clear();
+ }
+}
diff --git a/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java b/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java
new file mode 100644
index 0000000..521220b
--- /dev/null
+++ b/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java
@@ -0,0 +1,67 @@
+package autotest.planner.triage;
+
+import autotest.common.JsonRpcCallback;
+import autotest.common.JsonRpcProxy;
+import autotest.planner.TestPlanSelector;
+
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+
+public class TriageViewPresenter {
+
+ public interface Display {
+ public void setLoading(boolean loading);
+ public void clearAllFailureTables();
+ public FailureTable.Display generateFailureTable();
+ }
+
+ private TestPlanSelector selector;
+ private Display display;
+
+ public TriageViewPresenter(TestPlanSelector selector) {
+ this.selector = selector;
+ }
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+ public void refresh() {
+ String planId = selector.getSelectedPlan();
+ if (planId == null) {
+ return;
+ }
+
+ display.setLoading(true);
+
+ JSONObject params = new JSONObject();
+ params.put("plan_id", new JSONString(planId));
+
+ JsonRpcProxy.getProxy().rpcCall("get_failures", params, new JsonRpcCallback() {
+ @Override
+ public void onSuccess(JSONValue result) {
+ display.clearAllFailureTables();
+ generateFailureTables(result.isObject());
+ display.setLoading(false);
+ }
+ });
+ }
+
+ private void generateFailureTables(JSONObject failures) {
+ for (String group : failures.keySet()) {
+ FailureTable table = new FailureTable(group);
+ FailureTable.Display tableDisplay = display.generateFailureTable();
+ table.bindDisplay(tableDisplay);
+
+ JSONArray groupFailures = failures.get(group).isArray();
+
+ for (int i = 0; i < groupFailures.size(); i++) {
+ table.addFailure(groupFailures.get(i).isObject());
+ }
+
+ table.renderDisplay();
+ }
+ }
+}
diff --git a/frontend/client/src/autotest/planner/triage/TriageViewTab.java b/frontend/client/src/autotest/planner/triage/TriageViewTab.java
new file mode 100644
index 0000000..61f0840
--- /dev/null
+++ b/frontend/client/src/autotest/planner/triage/TriageViewTab.java
@@ -0,0 +1,35 @@
+package autotest.planner.triage;
+
+import autotest.common.ui.TabView;
+import autotest.planner.TestPlanSelector;
+
+import com.google.gwt.user.client.ui.HTMLPanel;
+
+
+public class TriageViewTab extends TabView {
+
+ private TriageViewPresenter presenter;
+ private TriageViewDisplay display = new TriageViewDisplay();
+
+ public TriageViewTab(TestPlanSelector selector) {
+ presenter = new TriageViewPresenter(selector);
+ }
+
+ @Override
+ public String getElementId() {
+ return "triage_view";
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ display.initialize((HTMLPanel) getWidget());
+ presenter.bindDisplay(display);
+ }
+
+ @Override
+ public void refresh() {
+ super.refresh();
+ presenter.refresh();
+ }
+}
diff --git a/frontend/client/src/autotest/public/TestPlannerClient.html b/frontend/client/src/autotest/public/TestPlannerClient.html
new file mode 100644
index 0000000..5fdfa5b
--- /dev/null
+++ b/frontend/client/src/autotest/public/TestPlannerClient.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Autotest Frontend</title>
+ <script type='text/javascript' src='autotest.TestPlannerClient.nocache.js'>
+ </script>
+ </head>
+
+ <body>
+ <!-- gwt history support -->
+ <iframe src="javascript:''" id="__gwt_historyFrame"
+ style="width:0;height:0;border:0"></iframe>
+
+
+ <div class="links-box" style="float: right;">
+ <span id="report_issues"></span> |
+ <a href="server/afe">Frontend</a> |
+ <a href="server/admin">Admin</a> |
+ <a href="/new_tko">Results</a>
+ (<a href="/tko"><b>Old</b> TKO</a>) |
+ Test Planner! |
+ <a href="http://wiki/Main/Autotest">Documentation</a> |
+ <a href="/g4sync.log">Synced CLs</a>
+ <div id="motd" class="motd"></div>
+ </div>
+
+ <img src="header.png" />
+ <br /><br />
+
+ <div id="tabs" class="hidden">
+ <div id="test_plan_selector">
+ Enter a test plan:
+ <span id="test_plan_selector_input"></span>
+ <span id="test_plan_selector_button"></span>
+ </div>
+
+ <div id="overview" title="Overview">
+ </div>
+
+ <div id="machine_view" title="Machine View">
+ </div>
+
+ <div id="test_view" title="Test View">
+ </div>
+
+ <div id="triage_view" title="Triage View">
+ <div id="triage_failure_tables"></div>
+ </div>
+
+ <div id="autoprocessed" title="Auto-processed">
+ </div>
+
+ <div id="history" title="History">
+ </div>
+ </div>
+ <br>
+ <div id="error_log"></div>
+
+ <!-- for debugging only -->
+ <div id="error_display"></div>
+ </body>
+</html>
diff --git a/frontend/client/src/autotest/tko/FragmentedTable.java b/frontend/client/src/autotest/tko/FragmentedTable.java
index 987e4c3..e69de29 100644
--- a/frontend/client/src/autotest/tko/FragmentedTable.java
+++ b/frontend/client/src/autotest/tko/FragmentedTable.java
@@ -1,143 +0,0 @@
-package autotest.tko;
-
-import autotest.common.ui.RightClickTable;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.HTMLTable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Customized table class supporting multiple tbody elements. It is modified to support input
- * handling, getRowCount(), getCellCount(), and getCellFormatter().getElement(). getElement()
- * also works. Calls to other methods aren't guaranteed to work.
- */
-class FragmentedTable extends RightClickTable {
- public class FragmentedCellFormatter extends HTMLTable.CellFormatter {
- @Override
- public Element getElement(int row, int column) {
- checkCellBounds(row, column);
- Element bodyElem = bodyElems.get(getFragmentIndex(row));
- return getCellElement(bodyElem, getRowWithinFragment(row), column);
- }
-
- /**
- * Native method to efficiently get a td element from a tbody. Copied from GWT's
- * HTMLTable.java.
- */
- private native Element getCellElement(Element tbody, int row, int col) /*-{
- return tbody.rows[row].cells[col];
- }-*/;
- }
-
- private List<Element> bodyElems = new ArrayList<Element>();
- private int totalRowCount;
- private int rowsPerFragment;
-
- public FragmentedTable() {
- super();
- setCellFormatter(new FragmentedCellFormatter());
- }
-
- /**
- * This method must be called after added or removing tbody elements and before using other
- * functionality (accessing cell elements, input handling, etc.).
- */
- public void updateBodyElems() {
- totalRowCount = 0;
- Element tbody = DOM.getFirstChild(getElement());
- for(; tbody != null; tbody = DOM.getNextSibling(tbody)) {
- assert tbody.getTagName().equalsIgnoreCase("tbody");
- bodyElems.add(tbody);
- totalRowCount += getRowCount(tbody);
- }
- }
-
- public void reset() {
- bodyElems.clear();
- TkoUtils.clearDomChildren(getElement());
- }
-
- private int getRowWithinFragment(int row) {
- return row % rowsPerFragment;
- }
-
- private int getFragmentIndex(int row) {
- return row / rowsPerFragment;
- }
-
- @Override
- public HTMLTable.Cell getCellForEvent(ClickEvent event) {
- return getCellForDomEvent(event);
- }
-
- @Override
- protected RowColumn getCellPosition(Element td) {
- Element tr = DOM.getParent(td);
- Element body = DOM.getParent(tr);
- int fragmentIndex = DOM.getChildIndex(getElement(), body);
- int rowWithinFragment = DOM.getChildIndex(body, tr);
- int row = fragmentIndex * rowsPerFragment + rowWithinFragment;
- int column = DOM.getChildIndex(tr, td);
- return new RowColumn(row, column);
- }
-
- /**
- * This is a modified version of getEventTargetCell() from HTMLTable.java.
- */
- @Override
- protected Element getEventTargetCell(Event event) {
- Element td = DOM.eventGetTarget(event);
- for (; td != null; td = DOM.getParent(td)) {
- // If it's a TD, it might be the one we're looking for.
- if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
- // Make sure it's directly a part of this table before returning
- // it.
- Element tr = DOM.getParent(td);
- Element body = DOM.getParent(tr);
- Element tableElem = DOM.getParent(body);
- if (tableElem == getElement()) {
- return td;
- }
- }
- // If we run into this table's element, we're out of options.
- if (td == getElement()) {
- return null;
- }
- }
- return null;
- }
-
- @Override
- public int getCellCount(int row) {
- Element bodyElem = bodyElems.get(getFragmentIndex(row));
- return getCellCount(bodyElem, getRowWithinFragment(row));
- }
-
- @Override
- public int getRowCount() {
- return totalRowCount;
- }
-
- private native int getRowCount(Element tbody) /*-{
- return tbody.rows.length;
- }-*/;
-
- private native int getCellCount(Element tbody, int row) /*-{
- return tbody.rows[row].cells.length;
- }-*/;
-
- /**
- * This must be called before using other functionality (accessing cell elements, input
- * handling, etc.).
- * @param rowsPerFragment The number of rows in each tbody. The last tbody may have fewer
- * rows. All others must have exactly this number of rows.
- */
- public void setRowsPerFragment(int rowsPerFragment) {
- this.rowsPerFragment = rowsPerFragment;
- }
-}
diff --git a/frontend/client/src/autotest/tko/Spreadsheet.java b/frontend/client/src/autotest/tko/Spreadsheet.java
index 4d25130..e69de29 100644
--- a/frontend/client/src/autotest/tko/Spreadsheet.java
+++ b/frontend/client/src/autotest/tko/Spreadsheet.java
@@ -1,587 +0,0 @@
-package autotest.tko;
-
-import autotest.common.UnmodifiableSublistView;
-import autotest.common.Utils;
-import autotest.common.ui.RightClickTable;
-
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.ScrollEvent;
-import com.google.gwt.event.dom.client.ScrollHandler;
-import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.IncrementalCommand;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlexTable;
-import com.google.gwt.user.client.ui.HTMLTable;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-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;
- private static final String BLANK_STRING = "(empty)";
- private static final int CELL_PADDING_PX = 2;
- 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>();
- private Map<Header, Integer> rowHeaderMap = new HashMap<Header, Integer>();
- private Map<Header, Integer> columnHeaderMap = new HashMap<Header, Integer>();
- protected CellInfo[][] dataCells, rowHeaderCells, columnHeaderCells;
- private RightClickTable rowHeaders = new RightClickTable();
- private RightClickTable columnHeaders = new RightClickTable();
- private FlexTable parentTable = new FlexTable();
- private FragmentedTable dataTable = new FragmentedTable();
- private int rowsPerIteration;
- 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() {
- }
-
- public HeaderImpl(Collection<? extends String> arg0) {
- super(arg0);
- }
-
- public static Header fromBaseType(List<String> baseType) {
- return new HeaderImpl(baseType);
- }
- }
-
- public static class CellInfo {
- public Header row, column;
- public String contents;
- public String color;
- 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,
- rowIndex, rowsPerIteration, true);
- rowIndex += rowsPerIteration;
- if (rowIndex > dataCells.length) {
- state++;
- }
- }
-
- public boolean execute() {
- switch (state) {
- case 0:
- computeRowsPerIteration();
- computeHeaderCells();
- break;
- case 1:
- renderHeaders();
- expandRowHeaders();
- break;
- case 2:
- // resize everything to the max dimensions (the window size)
- fillWindow(false);
- break;
- case 3:
- // set main table to match header sizes
- matchRowHeights(rowHeaders, dataCells);
- matchColumnWidths(columnHeaders, dataCells);
- dataTable.setVisible(false);
- break;
- case 4:
- // render the main data table
- renderSomeRows();
- return true;
- case 5:
- dataTable.updateBodyElems();
- dataTable.setVisible(true);
- break;
- case 6:
- // now expand headers as necessary
- // this can be very slow, so put it in it's own cycle
- matchRowHeights(dataTable, rowHeaderCells);
- break;
- case 7:
- matchColumnWidths(dataTable, columnHeaderCells);
- renderHeaders();
- break;
- case 8:
- // shrink the scroller if the table ended up smaller than the window
- fillWindow(true);
- DeferredCommand.addCommand(onFinished);
- return false;
- }
-
- state++;
- return true;
- }
- }
-
- 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);
- }
-
- private void setupTableInput(RightClickTable table) {
- table.addContextMenuHandler(this);
- table.addClickHandler(this);
- }
-
- protected void killPaddingAndSpacing(HTMLTable table) {
- table.setCellSpacing(0);
- table.setCellPadding(0);
- }
-
- /*
- * Wrap a widget with a panel that will clip its contents rather than grow
- * too much.
- */
- protected Panel wrapWithClipper(Widget w) {
- SimplePanel wrapper = new SimplePanel();
- wrapper.add(w);
- 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);
- assert !headerMap.containsKey(headerObject);
- 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
- */
- public void prepareForData() {
- dataCells = new CellInfo[rowHeaderValues.size()][columnHeaderValues.size()];
- }
-
- public CellInfo getCellInfo(int row, int column) {
- Header rowHeader = rowHeaderValues.get(row);
- Header columnHeader = columnHeaderValues.get(column);
- if (dataCells[row][column] == null) {
- dataCells[row][column] = new CellInfo(rowHeader, columnHeader, "");
- }
- 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.
- */
- public void render(IncrementalCommand onFinished) {
- DeferredCommand.addCommand(new RenderCommand(onFinished));
- }
-
- private void renderHeaders() {
- 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,
- boolean isRows) {
- int headerSize = fields.size();
- String[] lastFieldValue = new String[headerSize];
- CellInfo[] lastCellInfo = new CellInfo[headerSize];
- int[] counter = new int[headerSize];
- boolean newHeader;
- for (int headerIndex = 0; headerIndex < headerValues.size(); headerIndex++) {
- Header header = headerValues.get(headerIndex);
- newHeader = false;
- for (int fieldIndex = 0; fieldIndex < headerSize; fieldIndex++) {
- String fieldValue = header.get(fieldIndex);
- if (newHeader || !fieldValue.equals(lastFieldValue[fieldIndex])) {
- newHeader = true;
- Header currentHeader = getSubHeader(header, fieldIndex + 1);
- String cellContents = formatHeader(fields.get(fieldIndex), fieldValue);
- CellInfo cellInfo;
- if (isRows) {
- cellInfo = new CellInfo(currentHeader, null, cellContents);
- cells[headerIndex][fieldIndex] = cellInfo;
- } else {
- cellInfo = new CellInfo(null, currentHeader, cellContents);
- cells[fieldIndex][counter[fieldIndex]] = cellInfo;
- counter[fieldIndex]++;
- }
- lastFieldValue[fieldIndex] = fieldValue;
- lastCellInfo[fieldIndex] = cellInfo;
- } else {
- incrementSpan(lastCellInfo[fieldIndex], isRows);
- }
- }
- }
- }
-
- private String formatHeader(String field, String value) {
- if (value.equals("")) {
- return BLANK_STRING;
- }
- value = Utils.escape(value);
- if (field.equals("kernel")) {
- // line break after each /, for long paths
- value = value.replace("/", "/<br>").replace("/<br>/<br>", "//");
- }
- return value;
- }
-
- private void incrementSpan(CellInfo cellInfo, boolean isRows) {
- if (isRows) {
- cellInfo.rowSpan++;
- } else {
- cellInfo.colSpan++;
- }
- }
-
- private Header getSubHeader(Header header, int length) {
- if (length == header.size()) {
- return header;
- }
- List<String> subHeader = new UnmodifiableSublistView<String>(header, 0, length);
- return new HeaderImpl(subHeader);
- }
-
- private void matchRowHeights(HTMLTable from, CellInfo[][] to) {
- int lastColumn = to[0].length - 1;
- int rowCount = from.getRowCount();
- for (int row = 0; row < rowCount; row++) {
- int height = getRowHeight(from, row);
- 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;
- for (int column = 0; column < from.getCellCount(lastFromRow); column++) {
- int width = getColumnWidth(from, column);
- 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();
- if (div == null)
- return null;
- String contents = Utils.unescape(div.getInnerHTML());
- if (contents.equals(BLANK_STRING))
- contents = "";
- return contents;
- }
-
- public void clear() {
- rowHeaderValues.clear();
- columnHeaderValues.clear();
- rowHeaderMap.clear();
- 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() +
- columnHeaders.getOffsetHeight());
- newHeightPx = adjustMaxDimension(newHeightPx);
- 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));
- scrollPanel.setSize(getSizePxString(newWidthPx + SCROLLBAR_FUDGE),
- getSizePxString(newHeightPx + SCROLLBAR_FUDGE));
- }
-
- /**
- * 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,
- 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.
- */
- protected void expandRowHeaders() {
- 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;
- if (cellInfo.row == null) {
- tdElement = getCellElement(columnHeaders, 0, getColumnPosition(cellInfo.column));
- } else if (cellInfo.column == null) {
- tdElement = getCellElement(rowHeaders, getRowPosition(cellInfo.row), 0);
- } else {
- 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() -
- 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() -
- TD_BORDER_PX;
- }
-
- /**
- * Update floating headers.
- */
- @Override
- public void onScroll(ScrollEvent event) {
- int scrollLeft = scrollPanel.getHorizontalScrollPosition();
- int scrollTop = scrollPanel.getScrollPosition();
-
- setColumnHeadersOffset(-scrollLeft);
- setRowHeadersOffset(-scrollTop);
- }
-
- protected void setRowHeadersOffset(int offset) {
- rowHeaders.getElement().getStyle().setPropertyPx("top", offset);
- }
-
- 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;
- column = adjustRowHeaderColumnIndex(row, column);
- }
- else if (event.getSource() == columnHeaders) {
- cells = columnHeaderCells;
- }
- else {
- assert event.getSource() == dataTable;
- cells = dataCells;
- }
- 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
- * 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.
- */
- private int adjustRowHeaderColumnIndex(int row, int column) {
- for (int i = 0; i < rowFields.size(); i++) {
- if (rowHeaderCells[row][i] != null) {
- return i + column;
- }
- }
-
- throw new RuntimeException("Failed to find non-null cell");
- }
-
- public void setListener(SpreadsheetListener listener) {
- this.listener = listener;
- }
-
- public void setHighlighted(CellInfo cell, boolean highlighted) {
- Element cellElement = getCellElement(cell);
- if (highlighted) {
- cellElement.setClassName(HIGHLIGHTED_CLASS);
- } else {
- cellElement.setClassName("");
- }
- }
-
- public List<Integer> getAllTestIndices() {
- List<Integer> testIndices = new ArrayList<Integer>();
-
- for (CellInfo[] row : dataCells) {
- for (CellInfo cellInfo : row) {
- if (cellInfo != null && !cellInfo.isEmpty()) {
- testIndices.add(cellInfo.testIndex);
- }
- }
- }
-
- return testIndices;
- }
-}
diff --git a/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java b/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
index 147f695..bae2f06 100644
--- a/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
+++ b/frontend/client/src/autotest/tko/SpreadsheetDataProcessor.java
@@ -1,11 +1,12 @@
package autotest.tko;
+import autotest.common.spreadsheet.Spreadsheet;
+import autotest.common.spreadsheet.Spreadsheet.CellInfo;
+import autotest.common.spreadsheet.Spreadsheet.Header;
+import autotest.common.spreadsheet.Spreadsheet.HeaderImpl;
import autotest.common.table.DataSource.DataCallback;
import autotest.common.table.DataSource.Query;
import autotest.common.ui.NotifyManager;
-import autotest.tko.Spreadsheet.CellInfo;
-import autotest.tko.Spreadsheet.Header;
-import autotest.tko.Spreadsheet.HeaderImpl;
import com.google.gwt.core.client.Duration;
import com.google.gwt.json.client.JSONArray;
diff --git a/frontend/client/src/autotest/tko/SpreadsheetSelectionManager.java b/frontend/client/src/autotest/tko/SpreadsheetSelectionManager.java
index 9f8e034..e69de29 100644
--- a/frontend/client/src/autotest/tko/SpreadsheetSelectionManager.java
+++ b/frontend/client/src/autotest/tko/SpreadsheetSelectionManager.java
@@ -1,91 +0,0 @@
-package autotest.tko;
-
-import autotest.common.Utils;
-import autotest.tko.Spreadsheet.CellInfo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-
-// TODO: hopefully some of this could be combined with autotest.common.table.SelectionManager using
-// generics
-// TODO: get rid of header selection
-public class SpreadsheetSelectionManager {
- private Spreadsheet spreadsheet;
- private Collection<CellInfo> selectedCells = new HashSet<CellInfo>();
- private SpreadsheetSelectionListener listener;
-
- public static interface SpreadsheetSelectionListener {
- public void onCellsSelected(List<CellInfo> cells);
- public void onCellsDeselected(List<CellInfo> cells);
- }
-
- public SpreadsheetSelectionManager(Spreadsheet spreadsheet,
- SpreadsheetSelectionListener listener) {
- this.spreadsheet = spreadsheet;
- this.listener = listener;
- }
-
- public void toggleSelected(CellInfo cell) {
- if (selectedCells.contains(cell)) {
- deselectCell(cell);
- notifyDeselected(Utils.wrapObjectWithList(cell));
- } else {
- selectCell(cell);
- notifySelected(Utils.wrapObjectWithList(cell));
- }
- }
-
- private void selectCell(CellInfo cell) {
- selectedCells.add(cell);
- spreadsheet.setHighlighted(cell, true);
- }
-
- private void deselectCell(CellInfo cell) {
- selectedCells.remove(cell);
- spreadsheet.setHighlighted(cell, false);
- }
-
- public List<CellInfo> getSelectedCells() {
- return new ArrayList<CellInfo>(selectedCells);
- }
-
- public boolean isEmpty() {
- return selectedCells.isEmpty();
- }
-
- public void clearSelection() {
- List<CellInfo> cells = getSelectedCells();
- for (CellInfo cell : cells) {
- deselectCell(cell);
- }
- notifyDeselected(cells);
- }
-
- public void selectAll() {
- List<CellInfo> selectedCells = new ArrayList<CellInfo>();
- for (CellInfo[] row : spreadsheet.dataCells) {
- for (CellInfo cell : row) {
- if (cell == null || cell.isEmpty()) {
- continue;
- }
- selectCell(cell);
- selectedCells.add(cell);
- }
- }
- notifySelected(selectedCells);
- }
-
- private void notifyDeselected(List<CellInfo> cells) {
- if (listener != null) {
- listener.onCellsDeselected(cells);
- }
- }
-
- private void notifySelected(List<CellInfo> selectedCells) {
- if (listener != null) {
- listener.onCellsSelected(selectedCells);
- }
- }
-}
diff --git a/frontend/client/src/autotest/tko/SpreadsheetView.java b/frontend/client/src/autotest/tko/SpreadsheetView.java
index 6a7a6bf..11d3b6e 100644
--- a/frontend/client/src/autotest/tko/SpreadsheetView.java
+++ b/frontend/client/src/autotest/tko/SpreadsheetView.java
@@ -4,6 +4,11 @@
import autotest.common.JsonRpcProxy;
import autotest.common.Utils;
import autotest.common.CustomHistory.HistoryToken;
+import autotest.common.spreadsheet.Spreadsheet;
+import autotest.common.spreadsheet.SpreadsheetSelectionManager;
+import autotest.common.spreadsheet.Spreadsheet.CellInfo;
+import autotest.common.spreadsheet.Spreadsheet.Header;
+import autotest.common.spreadsheet.Spreadsheet.SpreadsheetListener;
import autotest.common.ui.ContextMenu;
import autotest.common.ui.NotifyManager;
import autotest.common.ui.SimpleHyperlink;
@@ -11,9 +16,6 @@
import autotest.common.ui.TableActionsPanel.TableActionsWithExportCsvListener;
import autotest.common.ui.TableSelectionPanel.SelectionPanelListener;
import autotest.tko.CommonPanel.CommonPanelListener;
-import autotest.tko.Spreadsheet.CellInfo;
-import autotest.tko.Spreadsheet.Header;
-import autotest.tko.Spreadsheet.SpreadsheetListener;
import autotest.tko.TableView.TableSwitchListener;
import autotest.tko.TableView.TableViewConfig;
diff --git a/frontend/client/src/autotest/tko/TableRenderer.java b/frontend/client/src/autotest/tko/TableRenderer.java
index deb94b8..e69de29 100644
--- a/frontend/client/src/autotest/tko/TableRenderer.java
+++ b/frontend/client/src/autotest/tko/TableRenderer.java
@@ -1,101 +0,0 @@
-package autotest.tko;
-
-import autotest.tko.Spreadsheet.CellInfo;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.HTMLTable;
-
-
-public class TableRenderer {
- // 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,
- int startRow, int maxRows, boolean renderNull) {
- StringBuffer htmlBuffer= new StringBuffer();
- htmlBuffer.append("<table><tbody>");
- for (int rowIndex = startRow; rowIndex < startRow + maxRows && rowIndex < rows.length;
- rowIndex++) {
- CellInfo[] row = rows[rowIndex];
- htmlBuffer.append("<tr>");
- for (CellInfo cell : row) {
- if (cell == null && renderNull) {
- 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.rowSpan > 1) {
- tdAttributes += attributeString("rowspan", Integer.toString(cell.rowSpan));
- }
- if (cell.colSpan > 1) {
- tdAttributes += attributeString("colspan", Integer.toString(cell.colSpan));
- }
-
- if (cell.widthPx != null) {
- divStyle += SIZE_PREFIX + "width: " + cell.widthPx + "px; ";
- }
- if (cell.heightPx != null) {
- divStyle += SIZE_PREFIX + "height: " + cell.heightPx + "px; ";
- }
- if (!divStyle.equals("")) {
- divAttributes += attributeString("style", divStyle);
- }
- if (cell.isEmpty()) {
- divAttributes += attributeString("class", NONCLICKABLE_CLASS);
- }
-
- htmlBuffer.append("<td " + tdAttributes + ">");
- htmlBuffer.append("<div " + divAttributes + ">");
- htmlBuffer.append(cell.contents);
- htmlBuffer.append("</div></td>");
- }
- }
- htmlBuffer.append("</tr>");
- }
- htmlBuffer.append("</tbody></table>");
-
- renderBody(tableObject, htmlBuffer.toString());
- }
-
- public void renderRows(HTMLTable tableObject, CellInfo[][] rows, boolean renderNull) {
- TkoUtils.clearDomChildren(tableObject.getElement()); // remove existing tbodies
- renderRowsAndAppend(tableObject, rows, 0, rows.length, renderNull);
- }
-
- public void renderRows(HTMLTable tableObject, CellInfo[][] rows) {
- renderRows(tableObject, rows, true);
- }
-
- private void renderBody(HTMLTable tableObject, String html) {
- // 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.
- */
- protected native void setBodyElement(HTMLTable table, Element newBody) /*-{
- table.@com.google.gwt.user.client.ui.HTMLTable::bodyElem = newBody;
- }-*/;
-
-}
diff --git a/frontend/client/src/autotest/tko/TkoUtils.java b/frontend/client/src/autotest/tko/TkoUtils.java
index 174c014..2c9529c 100644
--- a/frontend/client/src/autotest/tko/TkoUtils.java
+++ b/frontend/client/src/autotest/tko/TkoUtils.java
@@ -7,7 +7,6 @@
import autotest.common.table.RpcDataSource;
import autotest.common.table.DataSource.Query;
-import com.google.gwt.dom.client.Element;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
@@ -52,15 +51,6 @@
return params;
}
- protected static void clearDomChildren(Element elem) {
- Element child = elem.getFirstChildElement();
- while (child != null) {
- Element nextChild = child.getNextSiblingElement();
- elem.removeChild(child);
- child = nextChild;
- }
- }
-
static void setElementVisible(String elementId, boolean visible) {
DOM.getElementById(elementId).getStyle().setProperty("display", visible ? "" : "none");
}
diff --git a/frontend/planner/models.py b/frontend/planner/models.py
index f69aa28..48824d3 100644
--- a/frontend/planner/models.py
+++ b/frontend/planner/models.py
@@ -127,7 +127,7 @@
class TestConfig(ModelWithPlan, model_logic.ModelExtensions):
- """A planned test
+ """A configuration for a planned test
Required:
alias: The name to give this test within the plan. Unique with plan id
diff --git a/frontend/planner/rpc_interface.py b/frontend/planner/rpc_interface.py
index 219436f..ff04b2f 100644
--- a/frontend/planner/rpc_interface.py
+++ b/frontend/planner/rpc_interface.py
@@ -12,7 +12,7 @@
from autotest_lib.frontend.afe import model_logic, models as afe_models
from autotest_lib.frontend.afe import rpc_utils as afe_rpc_utils
from autotest_lib.frontend.tko import models as tko_models
-from autotest_lib.frontend.planner import models, rpc_utils
+from autotest_lib.frontend.planner import models, rpc_utils, model_attributes
from autotest_lib.client.common_lib import utils
# basic getter/setter calls
@@ -235,3 +235,47 @@
'hostname': hostname})
return updated
+
+
+def get_failures(plan_id):
+ """
+ Gets a list of the untriaged failures associated with this plan
+
+ @return a list of dictionaries:
+ id: the failure ID, for passing back to triage the failure
+ group: the group for the failure. Normally the same as the
+ reason, but can be different for custom queries
+ machine: the failed machine
+ blocked: True if the failure caused the machine to block
+ test_name: Concatenation of the Planner alias and the TKO test
+ name for the failed test
+ reason: test failure reason
+ seen: True if the failure is marked as "seen"
+ """
+ plan = models.Plan.smart_get(plan_id)
+ result = {}
+
+ failures = plan.testrun_set.filter(
+ finalized=True, triaged=False,
+ status=model_attributes.TestRunStatus.FAILED)
+ failures = failures.select_related('test_job__test', 'host__host',
+ 'tko_test')
+ for failure in failures:
+ test_name = '%s:%s' % (
+ failure.test_job.test_config.alias, failure.tko_test.test)
+
+ group_failures = result.setdefault(failure.tko_test.reason, [])
+ failure_dict = {'id': failure.id,
+ 'machine': failure.host.host.hostname,
+ 'blocked': bool(failure.host.blocked),
+ 'test_name': test_name,
+ 'reason': failure.tko_test.reason,
+ 'seen': bool(failure.seen)}
+ group_failures.append(failure_dict)
+
+ return result
+
+
+def get_static_data():
+ result = {'motd': afe_rpc_utils.get_motd()}
+ return result