Change Triage View's tables to ScrollTable, and implement bulk triage

Signed-off-by: James Ren <[email protected]>
Index: autotest/frontend/planner/rpc_utils_unittest.py
===================================================================
--- autotest/frontend/planner/rpc_utils_unittest.py	(revision 4446)
+++ autotest/frontend/planner/rpc_utils_unittest.py	(working copy)
@@ -3,9 +3,11 @@
 import unittest
 import common
 from autotest_lib.frontend import setup_django_environment
-from autotest_lib.frontend.planner import planner_test_utils
+from autotest_lib.frontend import setup_test_environment
 from autotest_lib.frontend.afe import model_logic, models as afe_models
+from autotest_lib.frontend.planner import planner_test_utils, model_attributes
 from autotest_lib.frontend.planner import models, rpc_utils, failure_actions
+from autotest_lib.frontend.tko import models as tko_models
 from autotest_lib.client.common_lib import utils, host_queue_entry_states
 
 
@@ -132,6 +134,58 @@
         self.assertTrue(self._planner_host.complete)
 
 
+    def test_process_failure(self):
+        self._setup_active_plan()
+        tko_test = tko_models.Test.objects.create(job=self._tko_job,
+                                                  machine=self._tko_machine,
+                                                  kernel=self._tko_kernel,
+                                                  status=self._running_status)
+        failure = models.TestRun.objects.create(
+                plan=self._plan,
+                test_job=self._planner_job,
+                tko_test=tko_test,
+                host=self._planner_host,
+                status=model_attributes.TestRunStatus.FAILED,
+                finalized=True, seen=False, triaged=False)
+        host_action = failure_actions.HostAction.UNBLOCK
+        test_action = failure_actions.TestAction.SKIP
+        labels = ['label1', 'label2']
+        keyvals = {'key1': 'value1',
+                   'key2': 'value2'}
+        bugs = ['bug1', 'bug2']
+        reason = 'overriden reason'
+        invalidate = True
+
+        self.god.stub_function(rpc_utils, '_process_host_action')
+        self.god.stub_function(rpc_utils, '_process_test_action')
+
+        rpc_utils._process_host_action.expect_call(self._planner_host,
+                                                   host_action)
+        rpc_utils._process_test_action.expect_call(self._planner_job,
+                                                   test_action)
+
+        rpc_utils.process_failure(
+                failure_id=failure.id, host_action=host_action,
+                test_action=test_action, labels=labels, keyvals=keyvals,
+                bugs=bugs, reason=reason, invalidate=invalidate)
+        failure = models.TestRun.objects.get(id=failure.id)
+
+        self.assertEqual(
+                set(failure.tko_test.testlabel_set.all()),
+                set(tko_models.TestLabel.objects.filter(name__in=labels)))
+        self.assertEqual(
+                set(failure.tko_test.job.jobkeyval_set.all()),
+                set(tko_models.JobKeyval.objects.filter(
+                        key__in=keyvals.iterkeys())))
+        self.assertEqual(set(failure.bugs.all()),
+                         set(models.Bug.objects.filter(external_uid__in=bugs)))
+        self.assertEqual(failure.tko_test.reason, reason)
+        self.assertEqual(failure.invalidated, invalidate)
+        self.assertTrue(failure.seen)
+        self.assertTrue(failure.triaged)
+        self.god.check_playback()
+
+
     def _replace_site_process_host_action(self, replacement):
         self.god.stub_function(utils, 'import_site_function')
         utils.import_site_function.expect_any_call().and_return(replacement)
@@ -149,7 +203,7 @@
                                           blocked=False)
         assert not host.blocked
 
-        rpc_utils.process_host_action(host, failure_actions.HostAction.BLOCK)
+        rpc_utils._process_host_action(host, failure_actions.HostAction.BLOCK)
         host = models.Host.objects.get(id=host.id)
 
         self.assertTrue(host.blocked)
@@ -162,7 +216,7 @@
                                           blocked=True)
         assert host.blocked
 
-        rpc_utils.process_host_action(host, failure_actions.HostAction.UNBLOCK)
+        rpc_utils._process_host_action(host, failure_actions.HostAction.UNBLOCK)
         host = models.Host.objects.get(id=host.id)
 
         self.assertFalse(host.blocked)
@@ -175,7 +229,7 @@
         failure_actions.HostAction.values.append(action)
         host = models.Host.objects.create(plan=self._plan, host=self.hosts[0])
 
-        self.assertRaises(AssertionError, rpc_utils.process_host_action,
+        self.assertRaises(AssertionError, rpc_utils._process_host_action,
                           host, action)
         self.god.check_playback()
 
@@ -185,7 +239,7 @@
             return True
         self._replace_site_process_host_action(_site_process_host_action)
 
-        rpc_utils.process_host_action(host, action)
+        rpc_utils._process_host_action(host, action)
 
         self.assertTrue(self._called)
         self.god.check_playback()
@@ -196,8 +250,8 @@
         planner_job = self._planner_job
         assert not planner_job.requires_rerun
 
-        rpc_utils.process_test_action(planner_job,
-                                      failure_actions.TestAction.SKIP)
+        rpc_utils._process_test_action(planner_job,
+                                       failure_actions.TestAction.SKIP)
         planner_job = models.Job.objects.get(id=planner_job.id)
 
         self.assertFalse(planner_job.requires_rerun)
@@ -208,8 +262,8 @@
         planner_job = self._planner_job
         assert not planner_job.requires_rerun
 
-        rpc_utils.process_test_action(planner_job,
-                                      failure_actions.TestAction.RERUN)
+        rpc_utils._process_test_action(planner_job,
+                                       failure_actions.TestAction.RERUN)
         planner_job = models.Job.objects.get(id=planner_job.id)
 
         self.assertTrue(planner_job.requires_rerun)
Index: autotest/frontend/planner/rpc_interface_unittest.py
===================================================================
--- autotest/frontend/planner/rpc_interface_unittest.py	(revision 4446)
+++ autotest/frontend/planner/rpc_interface_unittest.py	(working copy)
@@ -3,6 +3,7 @@
 import unittest
 import common
 from autotest_lib.frontend import setup_django_environment
+from autotest_lib.frontend import setup_test_environment
 from autotest_lib.frontend.planner import planner_test_utils, model_attributes
 from autotest_lib.frontend.planner import rpc_interface, models, rpc_utils
 from autotest_lib.frontend.planner import failure_actions
@@ -163,55 +164,5 @@
         self.god.check_playback()
 
 
-    def test_process_failure(self):
-        self._setup_active_plan()
-        tko_test = tko_models.Test.objects.create(job=self._tko_job,
-                                                  machine=self._tko_machine,
-                                                  kernel=self._tko_kernel,
-                                                  status=self._running_status)
-        failure = models.TestRun.objects.create(
-                plan=self._plan,
-                test_job=self._planner_job,
-                tko_test=tko_test,
-                host=self._planner_host,
-                status=model_attributes.TestRunStatus.FAILED,
-                finalized=True, seen=False, triaged=False)
-        host_action = failure_actions.HostAction.UNBLOCK
-        test_action = failure_actions.TestAction.SKIP
-        labels = ['label1', 'label2']
-        keyvals = {'key1': 'value1',
-                   'key2': 'value2'}
-        bugs = ['bug1', 'bug2']
-        reason = 'overriden reason'
-        invalidate = True
-
-        self.god.stub_function(rpc_utils, 'process_host_action')
-        self.god.stub_function(rpc_utils, 'process_test_action')
-
-        rpc_utils.process_host_action.expect_call(self._planner_host,
-                                                  host_action)
-        rpc_utils.process_test_action.expect_call(self._planner_job,
-                                                  test_action)
-
-        rpc_interface.process_failure(failure.id, host_action, test_action,
-                                      labels, keyvals, bugs, reason, invalidate)
-        failure = models.TestRun.objects.get(id=failure.id)
-
-        self.assertEqual(
-                set(failure.tko_test.testlabel_set.all()),
-                set(tko_models.TestLabel.objects.filter(name__in=labels)))
-        self.assertEqual(
-                set(failure.tko_test.job.jobkeyval_set.all()),
-                set(tko_models.JobKeyval.objects.filter(
-                        key__in=keyvals.iterkeys())))
-        self.assertEqual(set(failure.bugs.all()),
-                         set(models.Bug.objects.filter(external_uid__in=bugs)))
-        self.assertEqual(failure.tko_test.reason, reason)
-        self.assertEqual(failure.invalidated, invalidate)
-        self.assertTrue(failure.seen)
-        self.assertTrue(failure.triaged)
-        self.god.check_playback()
-
-
 if __name__ == '__main__':
     unittest.main()
Index: autotest/frontend/planner/rpc_utils.py
===================================================================
--- autotest/frontend/planner/rpc_utils.py	(revision 4446)
+++ autotest/frontend/planner/rpc_utils.py	(working copy)
@@ -3,6 +3,7 @@
 from autotest_lib.frontend.afe import models as afe_models, model_logic
 from autotest_lib.frontend.planner import models, model_attributes
 from autotest_lib.frontend.planner import failure_actions
+from autotest_lib.frontend.tko import models as tko_models
 from autotest_lib.client.common_lib import global_config, utils, global_config
 
 
@@ -169,11 +170,58 @@
     test_run.save()
 
 
+def process_failure(failure_id, host_action, test_action, labels, keyvals,
+                    bugs, reason, invalidate):
+    if keyvals is None:
+        keyvals = {}
+
+    failure = models.TestRun.objects.get(id=failure_id)
+
+    _process_host_action(failure.host, host_action)
+    _process_test_action(failure.test_job, test_action)
+
+    # Add the test labels
+    for label in labels:
+        tko_test_label, _ = (
+                tko_models.TestLabel.objects.get_or_create(name=label))
+        failure.tko_test.testlabel_set.add(tko_test_label)
+
+    # Set the job keyvals
+    for key, value in keyvals.iteritems():
+        keyval, created = tko_models.JobKeyval.objects.get_or_create(
+                job=failure.tko_test.job, key=key)
+        if not created:
+            tko_models.JobKeyval.objects.create(job=failure.tko_test.job,
+                                                key='original_' + key,
+                                                value=keyval.value)
+        keyval.value = value
+        keyval.save()
+
+    # Add the bugs
+    for bug_id in bugs:
+        bug, _ = models.Bug.objects.get_or_create(external_uid=bug_id)
+        failure.bugs.add(bug)
+
+    # Set the failure reason
+    if reason is not None:
+        tko_models.TestAttribute.objects.create(test=failure.tko_test,
+                                                attribute='original_reason',
+                                                value=failure.tko_test.reason)
+        failure.tko_test.reason = reason
+        failure.tko_test.save()
+
+    # Set 'invalidated', 'seen', and 'triaged'
+    failure.invalidated = invalidate
+    failure.seen = True
+    failure.triaged = True
+    failure.save()
+
+
 def _site_process_host_action_dummy(host, action):
     return False
 
 
-def process_host_action(host, action):
+def _process_host_action(host, action):
     """
     Takes the specified action on the host
     """
@@ -199,7 +247,7 @@
         host.save()
 
 
-def process_test_action(planner_job, action):
+def _process_test_action(planner_job, action):
     """
     Takes the specified action for this planner job
     """
Index: autotest/frontend/planner/rpc_interface.py
===================================================================
--- autotest/frontend/planner/rpc_interface.py	(revision 4446)
+++ autotest/frontend/planner/rpc_interface.py	(working copy)
@@ -254,10 +254,11 @@
     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')
+    failures = failures.order_by('seen').select_related('test_job__test',
+                                                        'host__host',
+                                                        'tko_test')
     for failure in failures:
-        test_name = '%s:%s' % (
+        test_name = '%s: %s' % (
                 failure.test_job.test_config.alias, failure.tko_test.test)
 
         group_failures = result.setdefault(failure.tko_test.reason, [])
@@ -309,8 +310,8 @@
     models.TestRun.objects.filter(id__in=failure_ids).update(seen=True)
 
 
-def process_failure(failure_id, host_action, test_action, labels=(),
-                    keyvals=None, bugs=(), reason=None, invalidate=False):
+def process_failures(failure_ids, host_action, test_action, labels=(),
+                     keyvals=None, bugs=(), reason=None, invalidate=False):
     """
     Triage a failure
 
@@ -325,9 +326,6 @@
     @param invalidate: True if failure should be invalidated for the purposes of
                        reporting. Defaults to False.
     """
-    if keyvals is None:
-        keyvals = {}
-
     host_choices = failure_actions.HostAction.values
     test_choices = failure_actions.TestAction.values
     if host_action not in host_choices:
@@ -339,53 +337,17 @@
                 {'test_action': ('test action %s not valid; must be one of %s'
                                  % (test_action, ', '.join(test_choices)))})
 
-    failure = models.TestRun.objects.get(id=failure_id)
+    for failure_id in failure_ids:
+        rpc_utils.process_failure(
+                failure_id=failure_id, host_action=host_action,
+                test_action=test_action, labels=labels, keyvals=keyvals,
+                bugs=bugs, reason=reason, invalidate=invalidate)
 
-    rpc_utils.process_host_action(failure.host, host_action)
-    rpc_utils.process_test_action(failure.test_job, test_action)
 
-    # Add the test labels
-    for label in labels:
-        tko_test_label, _ = (
-                tko_models.TestLabel.objects.get_or_create(name=label))
-        failure.tko_test.testlabel_set.add(tko_test_label)
-
-    # Set the job keyvals
-    for key, value in keyvals.iteritems():
-        keyval, created = tko_models.JobKeyval.objects.get_or_create(
-                job=failure.tko_test.job, key=key)
-        if not created:
-            tko_models.JobKeyval.objects.create(job=failure.tko_test.job,
-                                                key='original_' + key,
-                                                value=keyval.value)
-        keyval.value = value
-        keyval.save()
-
-    # Add the bugs
-    for bug_id in bugs:
-        bug, _ = models.Bug.objects.get_or_create(external_uid=bug_id)
-        failure.bugs.add(bug)
-
-    # Set the failure reason
-    if reason is not None:
-        tko_models.TestAttribute.objects.create(test=failure.tko_test,
-                                                attribute='original_reason',
-                                                value=failure.tko_test.reason)
-        failure.tko_test.reason = reason
-        failure.tko_test.save()
-
-    # Set 'invalidated', 'seen', and 'triaged'
-    failure.invalidated = invalidate
-    failure.seen = True
-    failure.triaged = True
-    failure.save()
-
-
 def get_motd():
     return afe_rpc_utils.get_motd()
 
 
-
 def get_static_data():
     result = {'motd': get_motd(),
               'host_actions': sorted(failure_actions.HostAction.values),
Index: autotest/frontend/client/src/autotest/planner/triage/FailureTable.java
===================================================================
--- autotest/frontend/client/src/autotest/planner/triage/FailureTable.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/planner/triage/FailureTable.java	(working copy)
@@ -1,26 +1,25 @@
 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 autotest.common.ui.NotifyManager;
-
+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.json.client.JSONObject;
-import com.google.gwt.user.client.IncrementalCommand;
+import com.google.gwt.user.client.ui.HasValue;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
-class FailureTable implements SpreadsheetListener, TriagePopup.Listener {
+class FailureTable implements ClickHandler {
     
-    public interface Display {
-        public Spreadsheet getSpreadsheet();
-        public TriagePopup.Display generateTriagePopupDisplay();
+    public static final String[] COLUMN_NAMES = {"Machine", "Test", "Reason"};
+    
+    public static interface Display {
+        public void addRow(String[] cells, boolean isNew);
+        public void setAllRowsSelected(boolean selected);
+        public HasClickHandlers getSelectAllControl();
+        public HasValue<Boolean> getSelectAllValue();
+        public Set<Integer> getSelectedFailures();
     }
     
     private static class Failure {
@@ -42,7 +41,8 @@
         }
         
         public static Failure fromJsonObject(JSONObject failureObj) {
-            return new Failure((int) failureObj.get("id").isNumber().doubleValue(),
+            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(),
@@ -52,139 +52,37 @@
     }
     
     private Display display;
-    private String group;
-    private LinkedList<Failure> failures = new LinkedList<Failure>();
-    private boolean rendered;
+    private List<Integer> failureIds = new ArrayList<Integer>();
     
-    public FailureTable(String group) {
-        this.group = group;
-    }
-    
     public void bindDisplay(Display display) {
         this.display = display;
-        display.getSpreadsheet().setListener(this);
+        display.getSelectAllControl().addClickHandler(this);
     }
     
     public void addFailure(JSONObject failureObj) {
-        setRendered(false);
-        
         Failure failure = Failure.fromJsonObject(failureObj);
         
-        if (failure.seen) {
-            failures.addLast(failure);
-        } else {
-            failures.addFirst(failure);
+        String machineDisplay = failure.machine;
+        if (failure.blocked) {
+            machineDisplay += " (blocked)";
         }
-    }
-    
-    public Display getDisplay() {
-        if (!rendered) {
-            renderDisplay();
-        }
         
-        return display;
+        display.addRow(
+                new String[] {machineDisplay, failure.testName, failure.reason}, !failure.seen);
+        failureIds.add(failure.id);
     }
-    
-    private void setRendered(boolean rendered) {
-        this.rendered = rendered;
-        display.getSpreadsheet().setVisible(rendered);
-    }
-    
-    public void renderDisplay() {
-        Spreadsheet spreadsheet = display.getSpreadsheet();
-        spreadsheet.clear();
-        
-        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;
-            test.testIndex = failure.id;
-            reason.testIndex = failure.id;
-            
-            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() {
-                setRendered(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) {
-        if (cellInfo.testIndex == 0) {
-            return;
-        }
-        
-        TriagePopup popup = new TriagePopup(this, cellInfo.testIndex);
-        popup.bindDisplay(display.generateTriagePopupDisplay());
-        popup.render();
+    public void onClick(ClickEvent event) {
+        assert event.getSource() == display.getSelectAllControl();
+        display.setAllRowsSelected(display.getSelectAllValue().getValue());
     }
-
-    @Override
-    public void onTriage(TriagePopup source) {
-        if (removeFailure(source.getId())) {
-            if (!failures.isEmpty()) {
-                // If no more failures, leave the spreadsheet invisible
-                renderDisplay();
-            }
-        }
-    }
     
-    private boolean removeFailure(int id) {
-        setRendered(false);
-        
-        Iterator<Failure> iter = failures.iterator();
-        while (iter.hasNext()) {
-            if (iter.next().id == id) {
-                iter.remove();
-                return true;
-            }
+    public List<Integer> getSelectedFailureIds() {
+        List<Integer> selected = new ArrayList<Integer>();
+        for (int i : display.getSelectedFailures()) {
+            selected.add(failureIds.get(i));
         }
-        
-        /*
-         * TODO: throw an Exception instead, and register a handler with
-         * GWT.setUncaughtExceptionHandler()
-         */
-        NotifyManager.getInstance().showError("Did not find failure id " + id);
-        setRendered(true);
-        return false;
+        return selected;
     }
 }
Index: autotest/frontend/client/src/autotest/planner/triage/TriagePopup.java
===================================================================
--- autotest/frontend/client/src/autotest/planner/triage/TriagePopup.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/planner/triage/TriagePopup.java	(working copy)
@@ -3,6 +3,7 @@
 import autotest.common.JsonRpcCallback;
 import autotest.common.JsonRpcProxy;
 import autotest.common.StaticDataRepository;
+import autotest.common.Utils;
 import autotest.common.ui.SimplifiedList;
 
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -13,7 +14,6 @@
 import com.google.gwt.event.logical.shared.HasCloseHandlers;
 import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.json.client.JSONBoolean;
-import com.google.gwt.json.client.JSONNumber;
 import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.json.client.JSONString;
 import com.google.gwt.json.client.JSONValue;
@@ -21,6 +21,8 @@
 import com.google.gwt.user.client.ui.HasValue;
 import com.google.gwt.user.client.ui.PopupPanel;
 
+import java.util.List;
+
 public class TriagePopup implements ClickHandler, CloseHandler<PopupPanel> {
     public static interface Display extends HasCloseHandlers<PopupPanel> {
         public HasClickHandlers getCloseButton();
@@ -37,17 +39,17 @@
     }
     
     public static interface Listener {
-        public void onTriage(TriagePopup source);
+        public void onTriage();
     }
     
     private Display display;
     private Listener listener;
-    private int id;
+    private List<Integer> ids;
     private boolean triaged = false;
     
-    public TriagePopup(Listener listener, int id) {
+    public TriagePopup(Listener listener, List<Integer> ids) {
         this.listener = listener;
-        this.id = id;
+        this.ids = ids;
     }
     
     public void bindDisplay(Display display) {
@@ -61,8 +63,8 @@
         display.center();
     }
     
-    public int getId() {
-        return id;
+    public List<Integer> getIds() {
+        return ids;
     }
     
     private void populateActionsFields() {
@@ -93,7 +95,7 @@
             
             JSONObject params = getParams();
             
-            proxy.rpcCall("process_failure", params, new JsonRpcCallback() {
+            proxy.rpcCall("process_failures", params, new JsonRpcCallback() {
                 @Override
                 public void onSuccess(JSONValue result) {
                     triaged = true;
@@ -105,7 +107,7 @@
     
     private JSONObject getParams() {
         JSONObject params = new JSONObject();
-        params.put("failure_id", new JSONNumber(id));
+        params.put("failure_ids", Utils.integersToJSON(ids));
         params.put("host_action", new JSONString(display.getHostActionField().getSelectedName()));
         params.put("test_action", new JSONString(display.getTestActionField().getSelectedName()));
         
@@ -146,7 +148,7 @@
     @Override
     public void onClose(CloseEvent<PopupPanel> event) {
         if (triaged) {
-            listener.onTriage(this);
+            listener.onTriage();
         }
     }
 }
Index: autotest/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java
===================================================================
--- autotest/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/planner/triage/FailureTableDisplay.java	(working copy)
@@ -1,24 +1,86 @@
 package autotest.planner.triage;
 
-import autotest.common.spreadsheet.Spreadsheet;
-
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.gen2.table.client.FixedWidthFlexTable;
+import com.google.gwt.gen2.table.client.FixedWidthGrid;
+import com.google.gwt.gen2.table.client.ScrollTable;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.ResizePolicy;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.ScrollPolicy;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.SortPolicy;
+import com.google.gwt.gen2.table.client.SelectionGrid.SelectionPolicy;
+import com.google.gwt.gen2.table.override.client.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasValue;
 
+import java.util.Set;
+
+
 public class FailureTableDisplay extends Composite implements FailureTable.Display {
     
-    private Spreadsheet spreadsheet = new Spreadsheet();
+    private FixedWidthGrid dataTable;
+    private ScrollTable scrollTable;
+    private CheckBox selectAll = new CheckBox();
     
-    public FailureTableDisplay() {
-        initWidget(spreadsheet);
+    public FailureTableDisplay(String group, String[] columnNames) {
+        FixedWidthFlexTable header = new FixedWidthFlexTable();
+        FlexCellFormatter formatter = header.getFlexCellFormatter();
+        
+        dataTable = new FixedWidthGrid(0, columnNames.length);
+        dataTable.setSelectionPolicy(SelectionPolicy.CHECKBOX);
+        
+        scrollTable = new ScrollTable(dataTable, header);
+        scrollTable.setSortPolicy(SortPolicy.DISABLED);
+        scrollTable.setResizePolicy(ResizePolicy.UNCONSTRAINED);
+        scrollTable.setScrollPolicy(ScrollPolicy.BOTH);
+        scrollTable.setHeight("200px");
+        
+        header.setText(0, 0, group);
+        header.setWidget(1, 0, selectAll);
+        
+        formatter.setColSpan(0, 0, columnNames.length + 1);
+        for (int i = 0; i < columnNames.length; i++) {
+            header.setText(1, i + 1, columnNames[i]);
+            header.setColumnWidth(i + 1, 1);
+            scrollTable.setHeaderColumnTruncatable(i, false);
+            scrollTable.setColumnTruncatable(i, false);
+        }
+        
+        initWidget(scrollTable);
     }
     
     @Override
-    public Spreadsheet getSpreadsheet() {
-        return spreadsheet;
+    public void addRow(String[] cells, boolean isNew) {
+        assert dataTable.getColumnCount() == cells.length;
+        
+        int row = dataTable.getRowCount();
+        dataTable.resizeRows(row + 1);
+        for (int cell = 0; cell < cells.length; cell++) {
+            dataTable.setText(row, cell, cells[cell]);
+            dataTable.setColumnWidth(cell, 1);
+        }
     }
-    
+
     @Override
-    public TriagePopup.Display generateTriagePopupDisplay() {
-        return new TriagePopupDisplay();
+    public HasClickHandlers getSelectAllControl() {
+        return selectAll;
     }
+
+    @Override
+    public HasValue<Boolean> getSelectAllValue() {
+        return selectAll;
+    }
+
+    public void setAllRowsSelected(boolean selected) {
+        if (selected) {
+            dataTable.selectAllRows();
+        } else {
+            dataTable.deselectAllRows();
+        }
+    }
+
+    @Override
+    public Set<Integer> getSelectedFailures() {
+        return dataTable.getSelectedRows();
+    }
 }
Index: autotest/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java
===================================================================
--- autotest/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/planner/triage/TriageViewDisplay.java	(working copy)
@@ -2,34 +2,75 @@
 
 import autotest.common.ui.NotifyManager;
 
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.VerticalPanel;
 
 
-public class TriageViewDisplay implements TriageViewPresenter.Display {
+public class TriageViewDisplay implements TriageViewPresenter.Display, ResizeHandler {
     
+    private static final int HEIGHT_FUDGE = 300;
+    
     private VerticalPanel container = new VerticalPanel();
+    private ScrollPanel scroll = new ScrollPanel(container);
+    private Button triage = new Button("Triage");
     
     public void initialize(HTMLPanel htmlPanel) {
         container.setSpacing(25);
-        htmlPanel.add(container, "triage_failure_tables");
+        container.setWidth("90%");
+        
+        scroll.setSize("100%", getHeightParam(Window.getClientHeight()));
+        scroll.setVisible(false);
+        triage.setVisible(false);
+        
+        htmlPanel.add(scroll, "triage_failure_tables");
+        htmlPanel.add(triage, "triage_button");
+        
+        Window.addResizeHandler(this);
     }
     
     @Override
     public void setLoading(boolean loading) {
         NotifyManager.getInstance().setLoading(loading);
-        container.setVisible(!loading);
+        scroll.setVisible(!loading);
+        
+        scroll.setVisible(true);
+        triage.setVisible(true);
     }
     
     @Override
-    public FailureTable.Display generateFailureTable() {
-        FailureTableDisplay display = new FailureTableDisplay();
+    public FailureTable.Display generateFailureTable(String group, String[] columnNames) {
+        FailureTableDisplay display = new FailureTableDisplay(group, columnNames);
         container.add(display);
         return display;
     }
+    
+    @Override
+    public TriagePopup.Display generateTriagePopupDisplay() {
+        return new TriagePopupDisplay();
+    }
 
     @Override
     public void clearAllFailureTables() {
         container.clear();
     }
+
+    @Override
+    public HasClickHandlers getTriageButton() {
+        return triage;
+    }
+
+    @Override
+    public void onResize(ResizeEvent event) {
+        scroll.setHeight(getHeightParam(event.getHeight()));
+    }
+    
+    private String getHeightParam(int height) {
+        return (height - HEIGHT_FUDGE) + "px";
+    }
 }
Index: autotest/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java
===================================================================
--- autotest/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/planner/triage/TriageViewPresenter.java	(working copy)
@@ -5,22 +5,32 @@
 import autotest.common.ui.HasTabVisible;
 import autotest.planner.TestPlanSelector;
 
+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.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 implements TestPlanSelector.Listener {
+import java.util.ArrayList;
+import java.util.List;
+
+public class TriageViewPresenter implements ClickHandler, TestPlanSelector.Listener,
+                                            TriagePopup.Listener {
     
     public interface Display {
         public void setLoading(boolean loading);
         public void clearAllFailureTables();
-        public FailureTable.Display generateFailureTable();
+        public FailureTable.Display generateFailureTable(String group, String[] columnNames);
+        public TriagePopup.Display generateTriagePopupDisplay();
+        public HasClickHandlers getTriageButton();
     }
     
     private TestPlanSelector selector;
     private Display display;
     private HasTabVisible tab;
+    private List<FailureTable> failureTables = new ArrayList<FailureTable>();
     
     public TriageViewPresenter(TestPlanSelector selector, HasTabVisible tab) {
         this.selector = selector;
@@ -30,6 +40,7 @@
     
     public void bindDisplay(Display display) {
         this.display = display;
+        display.getTriageButton().addClickHandler(this);
     }
     
     public void refresh() {
@@ -47,16 +58,25 @@
             @Override
             public void onSuccess(JSONValue result) {
                 display.clearAllFailureTables();
+                display.setLoading(false);
                 generateFailureTables(result.isObject());
+            }
+            
+            @Override
+            public void onError(JSONObject errorObject) {
+                super.onError(errorObject);
                 display.setLoading(false);
             }
         });
     }
     
-    private void generateFailureTables(JSONObject failures) {        
+    private void generateFailureTables(JSONObject failures) {
+        failureTables.clear();
+        
         for (String group : failures.keySet()) {
-            FailureTable table = new FailureTable(group);
-            FailureTable.Display tableDisplay = display.generateFailureTable();
+            FailureTable table = new FailureTable();
+            FailureTable.Display tableDisplay =
+                    display.generateFailureTable(group, FailureTable.COLUMN_NAMES);
             table.bindDisplay(tableDisplay);
             
             JSONArray groupFailures = failures.get(group).isArray();
@@ -65,7 +85,7 @@
                 table.addFailure(groupFailures.get(i).isObject());
             }
             
-            table.renderDisplay();
+            failureTables.add(table);
         }
     }
 
@@ -75,4 +95,23 @@
             refresh();
         }
     }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        assert event.getSource() == display.getTriageButton();
+        
+        List<Integer> failureIds = new ArrayList<Integer>();
+        for (FailureTable failure : failureTables) {
+            failureIds.addAll(failure.getSelectedFailureIds());
+        }
+        
+        TriagePopup popup = new TriagePopup(this, failureIds);
+        popup.bindDisplay(display.generateTriagePopupDisplay());
+        popup.render();
+    }
+
+    @Override
+    public void onTriage() {
+        refresh();
+    }
 }
Index: autotest/frontend/client/src/autotest/common/Utils.java
===================================================================
--- autotest/frontend/client/src/autotest/common/Utils.java	(revision 4446)
+++ autotest/frontend/client/src/autotest/common/Utils.java	(working copy)
@@ -39,6 +39,18 @@
         }
         return result;
     }
+    
+    /**
+     * Converts a collection of Java <code>Integers</code>s into a <code>JSONArray
+     * </code> of <code>JSONNumber</code>s.
+     */
+    public static JSONArray integersToJSON(Collection<Integer> integers) {
+        JSONArray result = new JSONArray();
+        for(Integer i : integers) {
+            result.set(result.size(), new JSONNumber(i));
+        }
+        return result;
+    }
 
     /**
      * Converts a <code>JSONArray</code> of <code>JSONStrings</code> to an 
Index: autotest/frontend/client/src/autotest/TestPlannerClient.gwt.xml
===================================================================
--- autotest/frontend/client/src/autotest/TestPlannerClient.gwt.xml	(revision 0)
+++ autotest/frontend/client/src/autotest/TestPlannerClient.gwt.xml	(revision 0)
@@ -0,0 +1,18 @@
+<module>
+  <inherits name='com.google.gwt.user.User'/>
+  <inherits name='com.google.gwt.json.JSON'/>
+  <inherits name='com.google.gwt.http.HTTP'/>
+  
+  <inherits name='com.google.gwt.widgetideas.WidgetIdeas'/>
+  <inherits name='com.google.gwt.gen2.table.ScrollTable'/>
+
+  <source path="planner"/>
+  <source path="common"/>
+  <entry-point class='autotest.planner.TestPlannerClient'/>
+
+  <stylesheet src='common.css'/>
+  <stylesheet src='standard.css'/>
+  <stylesheet src='scrolltable.css'/>
+  <stylesheet src='afeclient.css'/>
+  <stylesheet src='tkoclient.css'/>
+</module>
Index: autotest/frontend/client/src/autotest/public/TestPlannerClient.html
===================================================================
--- autotest/frontend/client/src/autotest/public/TestPlannerClient.html	(revision 4446)
+++ autotest/frontend/client/src/autotest/public/TestPlannerClient.html	(working copy)
@@ -46,6 +46,7 @@
 
       <div id="triage_view"  title="Triage View">
         <div id="triage_failure_tables"></div>
+        <div id="triage_button"></div>
       </div>
 
       <div id="autoprocessed" title="Auto-processed">
Index: autotest/frontend/client/src/autotest/public/scrolltable.css
===================================================================
--- autotest/frontend/client/src/autotest/public/scrolltable.css	(revision 0)
+++ autotest/frontend/client/src/autotest/public/scrolltable.css	(revision 0)
@@ -0,0 +1,43 @@
+.gwt-ScrollTable {
+  border-color: #aaa;
+  border-style: solid;
+  border-width: 1px 0px 1px 1px;
+}
+
+.gwt-ScrollTable .headerWrapper {
+  background: #8bd;
+}
+
+.gwt-ScrollTable .footerWrapper {
+  border-top: 1px solid #aaa;
+  background: #8bd;
+}
+
+.gwt-ScrollTable .dataTable td,
+.gwt-ScrollTable .headerTable td,
+.gwt-ScrollTable .footerTable td {
+  border-color: #aaa;
+  border-style: solid;
+  border-width: 0px 1px 1px 0px;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+.gwt-ScrollTable .headerTable td,
+.gwt-ScrollTable .footerTable td {
+  color: #fff;
+}
+
+.gwt-ScrollTable .dataTable tr.highlighted {
+  background: #C3D9FF;
+}
+
+.gwt-ScrollTable .dataTable td.highlighted {
+  background: #FFFFAA;
+  cursor: hand;
+  cursor: pointer;
+}
+
+.gwt-ScrollTable .dataTable tr.selected td {
+  background: #7AA5D6;
+}
_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest

Reply via email to