[autotest] Add time segmented filter on job table on View Host page.

Modified the frontend to provide a start time and an end time with
datetime pickers. The place holder values are set to midnight "today".
Please note that the place holder values are not default values.
They are equivalent to empty strings, but exist to avoid making users
have to fill in all six segments (year, month, day, hour, minute, am/pm).

On the rpc side, added start_time and end_time parameters to
get_host_queue_entries, get_num_host_queue_entries,
get_host_queue_entries_and_special_tasks,
and get_num_host_queue_entries_and_special_tasks.

A helper function inject_start_end_time_to_dict is used to inject start_time
and end_time to filter_datas.

BUG=chromium:362240
TEST=ran afe, passed rpc_interface_unittest and frontend_unittest
DEPLOY=afe, apache

Change-Id: I44d59124cc104cf55d2ac3838be983286e270195
Reviewed-on: https://chromium-review.googlesource.com/209389
Tested-by: Jiaxi Luo <jiaxiluo@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Jiaxi Luo <jiaxiluo@chromium.org>
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index 3d7cade..a8dcd6a 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -799,19 +799,29 @@
 
 # host queue entries
 
-def get_host_queue_entries(**filter_data):
+def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
     """\
     @returns A sequence of nested dictionaries of host and job information.
     """
+    filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
+                                                   'started_on__lte',
+                                                   start_time,
+                                                   end_time,
+                                                   **filter_data)
     return rpc_utils.prepare_rows_as_nested_dicts(
             models.HostQueueEntry.query_objects(filter_data),
             ('host', 'atomic_group', 'job'))
 
 
-def get_num_host_queue_entries(**filter_data):
+def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
     """\
     Get the number of host queue entries associated with this job.
     """
+    filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
+                                                   'started_on__lte',
+                                                   start_time,
+                                                   end_time,
+                                                   **filter_data)
     return models.HostQueueEntry.query_count(filter_data)
 
 
@@ -839,7 +849,8 @@
 # support for host detail view
 
 def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
-                                             query_limit=None):
+                                             query_limit=None, start_time=None,
+                                             end_time=None):
     """
     @returns an interleaved list of HostQueueEntries and SpecialTasks,
             in approximate run order.  each dict contains keys for type, host,
@@ -848,12 +859,18 @@
     total_limit = None
     if query_limit is not None:
         total_limit = query_start + query_limit
-    filter_data = {'host': host_id,
-                   'query_limit': total_limit,
-                   'sort_by': ['-id']}
+    filter_data_common = {'host': host_id,
+                          'query_limit': total_limit,
+                          'sort_by': ['-id']}
 
-    queue_entries = list(models.HostQueueEntry.query_objects(filter_data))
-    special_tasks = list(models.SpecialTask.query_objects(filter_data))
+    filter_data_queue_entries, filter_data_special_tasks = (
+            rpc_utils.inject_times_to_hqe_special_tasks_filters(
+                    filter_data_common, start_time, end_time))
+
+    queue_entries = list(models.HostQueueEntry.query_objects(
+            filter_data_queue_entries))
+    special_tasks = list(models.SpecialTask.query_objects(
+            filter_data_special_tasks))
 
     interleaved_entries = rpc_utils.interleave_entries(queue_entries,
                                                        special_tasks)
@@ -864,10 +881,16 @@
     return rpc_utils.prepare_for_serialization(interleaved_entries)
 
 
-def get_num_host_queue_entries_and_special_tasks(host_id):
-    filter_data = {'host': host_id}
-    return (models.HostQueueEntry.query_count(filter_data)
-            + models.SpecialTask.query_count(filter_data))
+def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
+                                                 end_time=None):
+    filter_data_common = {'host': host_id}
+
+    filter_data_queue_entries, filter_data_special_tasks = (
+            rpc_utils.inject_times_to_hqe_special_tasks_filters(
+                    filter_data_common, start_time, end_time))
+
+    return (models.HostQueueEntry.query_count(filter_data_queue_entries)
+            + models.SpecialTask.query_count(filter_data_special_tasks))
 
 
 # recurring run
diff --git a/frontend/afe/rpc_utils.py b/frontend/afe/rpc_utils.py
index 6cb369b..bccc23d 100644
--- a/frontend/afe/rpc_utils.py
+++ b/frontend/afe/rpc_utils.py
@@ -878,3 +878,40 @@
     return global_config.global_config.get_config_value('AUTOTEST_WEB',
                                                         'wmatrix_url',
                                                         default='')
+
+
+def inject_times_to_filter(start_time_key=None, end_time_key=None,
+                         start_time_value=None, end_time_value=None,
+                         **filter_data):
+    """Inject the key value pairs of start and end time if provided.
+
+    @param start_time_key: A string represents the filter key of start_time.
+    @param end_time_key: A string represents the filter key of end_time.
+    @param start_time_value: Start_time value.
+    @param end_time_value: End_time value.
+
+    @returns the injected filter_data.
+    """
+    if start_time_value:
+        filter_data[start_time_key] = start_time_value
+    if end_time_value:
+        filter_data[end_time_key] = end_time_value
+    return filter_data
+
+
+def inject_times_to_hqe_special_tasks_filters(filter_data_common,
+                                              start_time, end_time):
+    """Inject start and end time to hqe and special tasks filters.
+
+    @param filter_data_common: Common filter for hqe and special tasks.
+    @param start_time_key: A string represents the filter key of start_time.
+    @param end_time_key: A string represents the filter key of end_time.
+
+    @returns a pair of hqe and special tasks filters.
+    """
+    filter_data_special_tasks = filter_data_common.copy()
+    return (inject_times_to_filter('started_on__gte', 'started_on__lte',
+                                   start_time, end_time, **filter_data_common),
+           inject_times_to_filter('time_started__gte', 'time_started__lte',
+                                  start_time, end_time,
+                                  **filter_data_special_tasks))
\ No newline at end of file
diff --git a/frontend/client/src/autotest/afe/HostDetailView.java b/frontend/client/src/autotest/afe/HostDetailView.java
index 5846baa..639c81e 100644
--- a/frontend/client/src/autotest/afe/HostDetailView.java
+++ b/frontend/client/src/autotest/afe/HostDetailView.java
@@ -9,6 +9,7 @@
 import autotest.common.table.DataSource.Query;
 import autotest.common.table.DataSource.SortDirection;
 import autotest.common.table.DataTable;
+import autotest.common.table.DatetimeSegmentFilter;
 import autotest.common.table.DynamicTable;
 import autotest.common.table.DynamicTable.DynamicTableListener;
 import autotest.common.table.JSONObjectSet;
@@ -26,6 +27,8 @@
 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.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.json.client.JSONBoolean;
 import com.google.gwt.json.client.JSONObject;
@@ -77,6 +80,8 @@
 
         private SimpleFilter hostFilter = new SimpleFilter();
         private String hostId;
+        private String startTime;
+        private String endTime;
 
         public HostJobsTable() {
             super(HOST_JOBS_COLUMNS, normalDataSource);
@@ -88,6 +93,16 @@
             updateFilter();
         }
 
+        public void setStartTime(String startTime) {
+            this.startTime = startTime;
+            updateFilter();
+        }
+
+        public void setEndTime(String endTime) {
+            this.endTime = endTime;
+            updateFilter();
+        }
+
         private void updateFilter() {
             if (getDataSource() == normalDataSource) {
                 sortOnColumn("job__id", SortDirection.DESCENDING);
@@ -97,6 +112,10 @@
 
             hostFilter.clear();
             hostFilter.setParameter("host_id", new JSONString(hostId));
+            if (startTime != null && startTime != "")
+                hostFilter.setParameter("start_time", new JSONString(startTime));
+            if (endTime != null && endTime != "")
+                hostFilter.setParameter("end_time", new JSONString(endTime));
         }
 
         public void setSpecialTasksEnabled(boolean enabled) {
@@ -143,6 +162,7 @@
     private Button reinstallButton = new Button("Reinstall");
     private Button repairButton = new Button("Repair");
     private CheckBox showSpecialTasks = new CheckBox();
+    private DatetimeSegmentFilter startedTimeFilter = new DatetimeSegmentFilter();
     private TextBox hostnameInput = new TextBox();
     private Button hostnameFetchButton = new Button("Go");
 
@@ -306,6 +326,8 @@
         tableDecorator.addTableActionsPanel(this, true);
         tableDecorator.addControl("Show verifies, repairs, cleanups and resets",
                                   showSpecialTasks);
+        tableDecorator.addFilter("Filter by time started:",
+                                 startedTimeFilter);
         addWidget(tableDecorator, "view_host_jobs_table");
 
         showSpecialTasks.addClickHandler(new ClickHandler() {
@@ -322,6 +344,26 @@
                changeLock(!locked);
             }
         });
+        startedTimeFilter.addValueChangeHandler(
+            new ValueChangeHandler() {
+                public void onValueChange(ValueChangeEvent event) {
+                    String value = (String) event.getValue();
+                    jobsTable.setStartTime(value);
+                    if (value == "")
+                        startedTimeFilter.setStartTimeToPlaceHolderValue();
+                    jobsTable.refresh();
+                }
+            },
+            new ValueChangeHandler() {
+                public void onValueChange(ValueChangeEvent event) {
+                    String value = (String) event.getValue();
+                    jobsTable.setEndTime(value);
+                    if (value == "")
+                        startedTimeFilter.setEndTimeToPlaceHolderValue();
+                    jobsTable.refresh();
+                }
+            }
+        );
         addWidget(lockButton, "view_host_lock_button");
 
         reverifyButton.addClickHandler(new ClickHandler() {
diff --git a/frontend/client/src/autotest/common/table/DatetimeSegmentFilter.java b/frontend/client/src/autotest/common/table/DatetimeSegmentFilter.java
new file mode 100644
index 0000000..275dab8
--- /dev/null
+++ b/frontend/client/src/autotest/common/table/DatetimeSegmentFilter.java
@@ -0,0 +1,58 @@
+package autotest.common.table;
+
+import autotest.common.ui.DateTimeBox;
+
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.Date;
+
+public class DatetimeSegmentFilter extends SimpleFilter {
+    protected DateTimeBox startDatetimeBox;
+    protected DateTimeBox endDatetimeBox;
+    protected Panel panel;
+    protected Label fromLabel;
+    protected Label toLabel;
+    private String placeHolderDatetime;
+
+    public DatetimeSegmentFilter() {
+        startDatetimeBox = new DateTimeBox();
+        endDatetimeBox = new DateTimeBox();
+        fromLabel = new Label("From");
+        toLabel = new Label("to");
+
+        panel = new HorizontalPanel();
+        panel.add(fromLabel);
+        panel.add(startDatetimeBox);
+        panel.add(toLabel);
+        panel.add(endDatetimeBox);
+
+        DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat("yyyy-MM-dd");
+        placeHolderDatetime = dateTimeFormat.format(new Date()) + "T00:00";
+        setStartTimeToPlaceHolderValue();
+        setEndTimeToPlaceHolderValue();
+    }
+
+    @Override
+    public Widget getWidget() {
+        return panel;
+    }
+
+    public void setStartTimeToPlaceHolderValue() {
+        startDatetimeBox.setValue(placeHolderDatetime);
+    }
+
+    public void setEndTimeToPlaceHolderValue() {
+        endDatetimeBox.setValue(placeHolderDatetime);
+    }
+
+    public void addValueChangeHandler(ValueChangeHandler<String> startTimeHandler,
+                                      ValueChangeHandler<String> endTimeHandler) {
+        startDatetimeBox.addValueChangeHandler(startTimeHandler);
+        endDatetimeBox.addValueChangeHandler(endTimeHandler);
+    }
+}
\ No newline at end of file
diff --git a/frontend/client/src/autotest/common/ui/DateTimeBox.java b/frontend/client/src/autotest/common/ui/DateTimeBox.java
new file mode 100644
index 0000000..a88c953
--- /dev/null
+++ b/frontend/client/src/autotest/common/ui/DateTimeBox.java
@@ -0,0 +1,12 @@
+package autotest.common.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.InputElement;
+import com.google.gwt.user.client.ui.TextBoxBase;
+
+public class DateTimeBox extends TextBoxBase {
+    public DateTimeBox() {
+        super(Document.get().createTextInputElement());
+        getElement().setAttribute("type", "datetime-local");
+    }
+}
\ No newline at end of file