[autotest] Add job history support.

Add a module to be used to collect job history. For example, all special tasks
executed before and after the test job.
Add an rpc get_job_history to afe to get dictionary of the job and its special
tasks' start/end time.

Update AFE to add a button to show job history.

BUG=chromium:394445
TEST=local setup
./site_utils/job_history.py --job_id=4113
http://dshi.mtv.corp.google.com/afe/#tab_id=view_job&object_id=4113

Change-Id: I0da2e77aaf7e2a38158efd6a64d4f924cf0c803d
Reviewed-on: https://chromium-review.googlesource.com/210091
Tested-by: Dan Shi <dshi@chromium.org>
Reviewed-by: Fang Deng <fdeng@chromium.org>
Commit-Queue: Dan Shi <dshi@chromium.org>
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index fa6cb15..78048d1 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -24,6 +24,7 @@
 from autotest_lib.server.cros.dynamic_suite import job_status
 from autotest_lib.server.cros.dynamic_suite import tools
 from autotest_lib.server.hosts import moblab_host
+from autotest_lib.site_utils import job_history
 
 
 _CONFIG = global_config.global_config
@@ -251,3 +252,17 @@
     if not os.path.exists(boto_key):
         raise error.RPCException('Boto key: %s does not exist!' % boto_key)
     shutil.copyfile(boto_key, moblab_host.MOBLAB_BOTO_LOCATION)
+
+
+def get_job_history(**filter_data):
+    """Get history of the job, including the special tasks executed for the job
+
+    @param filter_data: filter for the call, should at least include
+                        {'job_id': [job id]}
+    @returns: JSON string of the job's history, including the information such
+              as the hosts run the job and the special tasks executed before
+              and after the job.
+    """
+    job_id = filter_data['job_id']
+    job_info = job_history.get_job_info(job_id)
+    return _rpc_utils().prepare_for_serialization(job_info.get_history())
diff --git a/frontend/client/src/autotest/afe/AfeUtils.java b/frontend/client/src/autotest/afe/AfeUtils.java
index 1b5497c..361ce0f 100644
--- a/frontend/client/src/autotest/afe/AfeUtils.java
+++ b/frontend/client/src/autotest/afe/AfeUtils.java
@@ -436,4 +436,21 @@
         }
         row.put(targetFieldName, new JSONString(date));
     }
+
+    public static void callGetJobHistory(JSONObject params,
+                                         final SimpleCallback onSuccess,
+                                         final boolean showMessage) {
+        JsonRpcProxy rpcProxy = JsonRpcProxy.getProxy();
+        rpcProxy.rpcCall("get_job_history", params, new JsonRpcCallback() {
+            @Override
+            public void onSuccess(JSONValue result) {
+                if (showMessage) {
+                    NotifyManager.getInstance().showMessage("Get job history succeeded.");
+                }
+                if (onSuccess != null) {
+                    onSuccess.doCallback(result);
+                }
+            }
+        });
+    }
 }
diff --git a/frontend/client/src/autotest/afe/JobDetailView.java b/frontend/client/src/autotest/afe/JobDetailView.java
index c398c70..b881247 100644
--- a/frontend/client/src/autotest/afe/JobDetailView.java
+++ b/frontend/client/src/autotest/afe/JobDetailView.java
@@ -5,6 +5,7 @@
 import autotest.common.StaticDataRepository;
 import autotest.common.Utils;
 import autotest.common.table.DataTable;
+import autotest.common.table.DataTable.DataTableListener;
 import autotest.common.table.DynamicTable;
 import autotest.common.table.ListFilter;
 import autotest.common.table.RpcDataSource;
@@ -36,6 +37,8 @@
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Widget;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class JobDetailView extends DetailView implements TableWidgetFactory {
@@ -51,6 +54,11 @@
         { "control_type", "Client/Server" }, { JobTable.HOSTS_SUMMARY, "Status" },
         { JobTable.RESULTS_SUMMARY, "Passed Tests" }
     };
+    private static final String[][] JOB_HISTORY_COLUMNS = {
+        { "id", "ID" }, { "hostname", "Host" }, { "name", "Name" },
+        { "start_time", "Start Time" }, { "end_time", "End Time" },
+        { "time_used", "Time Used (seconds)" }, { "status", "Status" }
+    };
     public static final String NO_URL = "about:blank";
     public static final int NO_JOB_ID = -1;
     public static final int HOSTS_PER_PAGE = 30;
@@ -70,6 +78,12 @@
         }
     }
 
+    protected class JobHistoryListener {
+        public void onJobSelected(String url) {
+            Utils.openUrlInNewWindow(url);
+        }
+    }
+
     protected int jobId = NO_JOB_ID;
 
     private JobStatusDataSource jobStatusDataSource = new JobStatusDataSource();
@@ -94,6 +108,10 @@
 
     protected StaticDataRepository staticData = StaticDataRepository.getRepository();
 
+    protected Button getJobHistoryButton = new Button("Get Job History");
+    protected JobHistoryListener jobHistoryListener = new JobHistoryListener();
+    protected DataTable jobHistoryTable = new DataTable(JOB_HISTORY_COLUMNS);
+
     public JobDetailView(JobDetailListener listener) {
         this.listener = listener;
         setupSpreadsheetListener(Utils.getBaseUrl());
@@ -193,6 +211,8 @@
 
                 parentJobIdFliter.setParameter("parent_job", new JSONNumber(jobId));
                 childJobsTable.refresh();
+
+                jobHistoryTable.clear();
             }
 
 
@@ -309,6 +329,24 @@
         if (!staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) {
             AfeUtils.removeElement("view_drone_set_wrapper");
         }
+
+        getJobHistoryButton.addClickHandler(new ClickHandler() {
+            public void onClick(ClickEvent event) {
+                getJobHistory();
+            }
+        });
+        addWidget(getJobHistoryButton, "view_get_job_history");
+
+        jobHistoryTable.setClickable(true);
+        jobHistoryTable.addListener(new DataTableListener() {
+            public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
+                String log_url= row.get("log_url").isString().stringValue();
+                jobHistoryListener.onJobSelected(log_url);
+            }
+
+            public void onTableRefreshed() {}
+        });
+        addWidget(jobHistoryTable, "job_history_table");
     }
 
     protected void addTableFilters() {
@@ -536,4 +574,19 @@
         url = Utils.getRetrieveLogsUrl(url);
         return "<a target=\"_blank\" href=\"" + url + "\">" + text + "</a>";
     }
+
+    private void getJobHistory() {
+        JSONObject params = new JSONObject();
+        params.put("job_id", new JSONNumber(jobId));
+        AfeUtils.callGetJobHistory(params, new SimpleCallback() {
+            public void doCallback(Object result) {
+                jobHistoryTable.clear();
+                List<JSONObject> rows = new ArrayList<JSONObject>();
+                JSONArray history = (JSONArray)result;
+                for (int i = 0; i < history.size(); i++)
+                    rows.add((JSONObject)history.get(i));
+                jobHistoryTable.addRows(rows);
+            }
+        }, true);
+    }
 }
diff --git a/frontend/client/src/autotest/public/AfeClient.html b/frontend/client/src/autotest/public/AfeClient.html
index f278da4..15e714a 100644
--- a/frontend/client/src/autotest/public/AfeClient.html
+++ b/frontend/client/src/autotest/public/AfeClient.html
@@ -159,6 +159,10 @@
 
           <span class="field-name">Child jobs</span>
           <div id="child_jobs_table"></div><br>
+
+          <span class="field-name">Job history</span>
+          <span id="view_get_job_history" />
+          <div id="job_history_table"></div><br>
         </div>
       </div>