Home | History | Annotate | Download | only in afe
      1 package autotest.afe;
      2 
      3 import autotest.common.JsonRpcCallback;
      4 import autotest.common.SimpleCallback;
      5 import autotest.common.StaticDataRepository;
      6 import autotest.common.Utils;
      7 import autotest.common.table.DataTable;
      8 import autotest.common.table.DataTable.DataTableListener;
      9 import autotest.common.table.DynamicTable;
     10 import autotest.common.table.ListFilter;
     11 import autotest.common.table.RpcDataSource;
     12 import autotest.common.table.SearchFilter;
     13 import autotest.common.table.SelectionManager;
     14 import autotest.common.table.SimpleFilter;
     15 import autotest.common.table.TableDecorator;
     16 import autotest.common.table.DataTable.TableWidgetFactory;
     17 import autotest.common.table.DynamicTable.DynamicTableListener;
     18 import autotest.common.ui.ContextMenu;
     19 import autotest.common.ui.DetailView;
     20 import autotest.common.ui.NotifyManager;
     21 import autotest.common.ui.TableActionsPanel.TableActionsListener;
     22 
     23 import com.google.gwt.dom.client.Element;
     24 import com.google.gwt.event.dom.client.ClickEvent;
     25 import com.google.gwt.event.dom.client.ClickHandler;
     26 import com.google.gwt.json.client.JSONArray;
     27 import com.google.gwt.json.client.JSONBoolean;
     28 import com.google.gwt.json.client.JSONNumber;
     29 import com.google.gwt.json.client.JSONObject;
     30 import com.google.gwt.json.client.JSONString;
     31 import com.google.gwt.json.client.JSONValue;
     32 import com.google.gwt.user.client.Command;
     33 import com.google.gwt.user.client.ui.Button;
     34 import com.google.gwt.user.client.ui.DisclosurePanel;
     35 import com.google.gwt.user.client.ui.Frame;
     36 import com.google.gwt.user.client.ui.HTML;
     37 import com.google.gwt.user.client.ui.Label;
     38 import com.google.gwt.user.client.ui.Widget;
     39 
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 import java.util.Set;
     43 
     44 public class JobDetailView extends DetailView implements TableWidgetFactory {
     45     private static final String[][] JOB_HOSTS_COLUMNS = {
     46         {DataTable.CLICKABLE_WIDGET_COLUMN, ""}, // selection checkbox
     47         {"hostname", "Host"}, {"full_status", "Status"},
     48         {"host_status", "Host Status"}, {"host_locked", "Host Locked"},
     49         // columns for all logs and debug log links
     50         {DataTable.CLICKABLE_WIDGET_COLUMN, ""}, {DataTable.CLICKABLE_WIDGET_COLUMN, ""}
     51     };
     52     private static final String[][] CHILD_JOBS_COLUMNS = {
     53         { "id", "ID" }, { "name", "Name" }, { "priority", "Priority" },
     54         { "control_type", "Client/Server" }, { JobTable.HOSTS_SUMMARY, "Status" },
     55         { JobTable.RESULTS_SUMMARY, "Passed Tests" }
     56     };
     57     private static final String[][] JOB_HISTORY_COLUMNS = {
     58         { "id", "ID" }, { "hostname", "Host" }, { "name", "Name" },
     59         { "start_time", "Start Time" }, { "end_time", "End Time" },
     60         { "time_used", "Time Used (seconds)" }, { "status", "Status" }
     61     };
     62     public static final String NO_URL = "about:blank";
     63     public static final int NO_JOB_ID = -1;
     64     public static final int HOSTS_PER_PAGE = 30;
     65     public static final int CHILD_JOBS_PER_PAGE = 30;
     66     public static final String RESULTS_MAX_WIDTH = "700px";
     67     public static final String RESULTS_MAX_HEIGHT = "500px";
     68 
     69     public interface JobDetailListener {
     70         public void onHostSelected(String hostId);
     71         public void onCloneJob(JSONValue result);
     72     }
     73 
     74     protected class ChildJobsListener {
     75         public void onJobSelected(int id) {
     76             fetchById(Integer.toString(id));
     77         }
     78     }
     79 
     80     protected class JobHistoryListener {
     81         public void onJobSelected(String url) {
     82             Utils.openUrlInNewWindow(url);
     83         }
     84     }
     85 
     86     protected int jobId = NO_JOB_ID;
     87 
     88     private JobStatusDataSource jobStatusDataSource = new JobStatusDataSource();
     89     protected JobTable childJobsTable = new JobTable(CHILD_JOBS_COLUMNS);
     90     protected TableDecorator childJobsTableDecorator = new TableDecorator(childJobsTable);
     91     protected SimpleFilter parentJobIdFliter = new SimpleFilter();
     92     protected DynamicTable hostsTable = new DynamicTable(JOB_HOSTS_COLUMNS, jobStatusDataSource);
     93     protected TableDecorator hostsTableDecorator = new TableDecorator(hostsTable);
     94     protected SimpleFilter jobFilter = new SimpleFilter();
     95     protected Button abortButton = new Button("Abort job");
     96     protected Button cloneButton = new Button("Clone job");
     97     protected Frame tkoResultsFrame = new Frame();
     98 
     99     protected JobDetailListener listener;
    100     protected ChildJobsListener childJobsListener = new ChildJobsListener();
    101     private SelectionManager hostsSelectionManager;
    102     private SelectionManager childJobsSelectionManager;
    103 
    104     private Label controlFile = new Label();
    105     private DisclosurePanel controlFilePanel = new DisclosurePanel("");
    106 
    107     protected StaticDataRepository staticData = StaticDataRepository.getRepository();
    108 
    109     protected Button getJobHistoryButton = new Button("Get Job History");
    110     protected JobHistoryListener jobHistoryListener = new JobHistoryListener();
    111     protected DataTable jobHistoryTable = new DataTable(JOB_HISTORY_COLUMNS);
    112 
    113     public JobDetailView(JobDetailListener listener) {
    114         this.listener = listener;
    115         setupSpreadsheetListener(Utils.getBaseUrl());
    116     }
    117 
    118     private native void setupSpreadsheetListener(String baseUrl) /*-{
    119         var ins = this;
    120         $wnd.onSpreadsheetLoad = function(event) {
    121             if (event.origin !== baseUrl) {
    122                 return;
    123             }
    124             ins. (at) autotest.afe.JobDetailView::resizeResultsFrame(Ljava/lang/String;)(event.data);
    125         }
    126 
    127         $wnd.addEventListener("message", $wnd.onSpreadsheetLoad, false);
    128     }-*/;
    129 
    130     @SuppressWarnings("unused") // called from native
    131     private void resizeResultsFrame(String message) {
    132         String[] parts = message.split(" ");
    133         tkoResultsFrame.setSize(parts[0], parts[1]);
    134     }
    135 
    136     @Override
    137     protected void fetchData() {
    138         pointToResults(NO_URL, NO_URL, NO_URL, NO_URL, NO_URL);
    139         JSONObject params = new JSONObject();
    140         params.put("id", new JSONNumber(jobId));
    141         rpcProxy.rpcCall("get_jobs_summary", params, new JsonRpcCallback() {
    142             @Override
    143             public void onSuccess(JSONValue result) {
    144                 JSONObject jobObject;
    145                 try {
    146                     jobObject = Utils.getSingleObjectFromArray(result.isArray());
    147                 }
    148                 catch (IllegalArgumentException exc) {
    149                     NotifyManager.getInstance().showError("No such job found");
    150                     resetPage();
    151                     return;
    152                 }
    153                 String name = Utils.jsonToString(jobObject.get("name"));
    154                 String runVerify = Utils.jsonToString(jobObject.get("run_verify"));
    155 
    156                 showText(name, "view_label");
    157                 showField(jobObject, "owner", "view_owner");
    158                 String parent_job_url = Utils.jsonToString(jobObject.get("parent_job")).trim();
    159                 if (parent_job_url.equals("<null>")){
    160                     parent_job_url = "http://www.youtube.com/watch?v=oHg5SJYRHA0";
    161                 } else {
    162                     parent_job_url = "#tab_id=view_job&object_id=" + parent_job_url;
    163                 }
    164                 showField(jobObject, "parent_job", "view_parent");
    165                 getElementById("view_parent").setAttribute("href", parent_job_url);
    166                 showField(jobObject, "test_retry", "view_test_retry");
    167                 double priorityValue = jobObject.get("priority").isNumber().getValue();
    168                 String priorityName = staticData.getPriorityName(priorityValue);
    169                 showText(priorityName, "view_priority");
    170                 showField(jobObject, "created_on", "view_created");
    171                 showField(jobObject, "timeout_mins", "view_timeout");
    172                 String imageUrlString = "";
    173                 if (jobObject.containsKey("image")) {
    174                     imageUrlString = Utils.jsonToString(jobObject.get("image")).trim();
    175                 }
    176                 showText(imageUrlString, "view_image_url");
    177                 showField(jobObject, "max_runtime_mins", "view_max_runtime");
    178                 showField(jobObject, "email_list", "view_email_list");
    179                 showText(runVerify, "view_run_verify");
    180                 showField(jobObject, "reboot_before", "view_reboot_before");
    181                 showField(jobObject, "reboot_after", "view_reboot_after");
    182                 showField(jobObject, "parse_failed_repair", "view_parse_failed_repair");
    183                 showField(jobObject, "synch_count", "view_synch_count");
    184                 if (jobObject.get("require_ssp").isNull() != null)
    185                     showText("false", "view_require_ssp");
    186                 else {
    187                     String require_ssp = Utils.jsonToString(jobObject.get("require_ssp"));
    188                     showText(require_ssp, "view_require_ssp");
    189                 }
    190                 showField(jobObject, "dependencies", "view_dependencies");
    191 
    192                 if (staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) {
    193                     showField(jobObject, "drone_set", "view_drone_set");
    194                 }
    195 
    196                 String header = Utils.jsonToString(jobObject.get("control_type")) + " control file";
    197                 controlFilePanel.getHeaderTextAccessor().setText(header);
    198                 controlFile.setText(Utils.jsonToString(jobObject.get("control_file")));
    199 
    200                 JSONObject counts = jobObject.get("status_counts").isObject();
    201                 String countString = AfeUtils.formatStatusCounts(counts, ", ");
    202                 showText(countString, "view_status");
    203                 abortButton.setVisible(isAnyEntryAbortable(counts));
    204 
    205                 String shard_url = Utils.jsonToString(jobObject.get("shard")).trim();
    206                 String job_id = Utils.jsonToString(jobObject.get("id")).trim();
    207                 if (shard_url.equals("<null>")){
    208                     shard_url = "";
    209                 } else {
    210                     shard_url = "http://" + shard_url;
    211                 }
    212                 shard_url = shard_url + "/afe/#tab_id=view_job&object_id=" + job_id;
    213                 showField(jobObject, "shard", "view_job_on_shard");
    214                 getElementById("view_job_on_shard").setAttribute("href", shard_url);
    215                 getElementById("view_job_on_shard").setInnerHTML(shard_url);
    216 
    217                 String jobTag = AfeUtils.getJobTag(jobObject);
    218                 pointToResults(getResultsURL(jobId), getLogsURL(jobTag),
    219                                getOldResultsUrl(jobId), getTriageUrl(jobId),
    220                                getEmbeddedUrl(jobId));
    221 
    222                 String jobTitle = "Job: " + name + " (" + jobTag + ")";
    223                 displayObjectData(jobTitle);
    224 
    225                 jobFilter.setParameter("job", new JSONNumber(jobId));
    226                 hostsTable.refresh();
    227 
    228                 parentJobIdFliter.setParameter("parent_job", new JSONNumber(jobId));
    229                 childJobsTable.refresh();
    230 
    231                 jobHistoryTable.clear();
    232 
    233                 getSpongeUrl(jobObject);
    234             }
    235 
    236 
    237             @Override
    238             public void onError(JSONObject errorObject) {
    239                 super.onError(errorObject);
    240                 resetPage();
    241             }
    242         });
    243     }
    244 
    245     protected boolean isAnyEntryAbortable(JSONObject statusCounts) {
    246         Set<String> statuses = statusCounts.keySet();
    247         for (String status : statuses) {
    248             if (!(status.equals("Completed") ||
    249                   status.equals("Failed") ||
    250                   status.equals("Stopped") ||
    251                   status.startsWith("Aborted"))) {
    252                 return true;
    253             }
    254         }
    255         return false;
    256     }
    257 
    258     @Override
    259     public void initialize() {
    260         super.initialize();
    261 
    262         idInput.setVisibleLength(5);
    263 
    264         childJobsTable.setRowsPerPage(CHILD_JOBS_PER_PAGE);
    265         childJobsTable.setClickable(true);
    266         childJobsTable.addListener(new DynamicTableListener() {
    267             public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
    268                 int jobId = (int) row.get("id").isNumber().doubleValue();
    269                 childJobsListener.onJobSelected(jobId);
    270             }
    271 
    272             public void onTableRefreshed() {}
    273         });
    274 
    275         childJobsTableDecorator.addPaginators();
    276         childJobsSelectionManager = childJobsTableDecorator.addSelectionManager(false);
    277         childJobsTable.setWidgetFactory(childJobsSelectionManager);
    278         addWidget(childJobsTableDecorator, "child_jobs_table");
    279 
    280         hostsTable.setRowsPerPage(HOSTS_PER_PAGE);
    281         hostsTable.setClickable(true);
    282         hostsTable.addListener(new DynamicTableListener() {
    283             public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
    284                 JSONObject host = row.get("host").isObject();
    285                 String id = host.get("id").toString();
    286                 listener.onHostSelected(id);
    287             }
    288 
    289             public void onTableRefreshed() {}
    290         });
    291         hostsTable.setWidgetFactory(this);
    292 
    293         hostsTableDecorator.addPaginators();
    294         addTableFilters();
    295         hostsSelectionManager = hostsTableDecorator.addSelectionManager(false);
    296         hostsTableDecorator.addTableActionsPanel(new TableActionsListener() {
    297             public ContextMenu getActionMenu() {
    298                 ContextMenu menu = new ContextMenu();
    299 
    300                 menu.addItem("Abort hosts", new Command() {
    301                     public void execute() {
    302                         abortSelectedHosts();
    303                     }
    304                 });
    305 
    306                 menu.addItem("Clone job on selected hosts", new Command() {
    307                     public void execute() {
    308                         cloneJobOnSelectedHosts();
    309                     }
    310                 });
    311 
    312                 if (hostsSelectionManager.isEmpty())
    313                     menu.setEnabled(false);
    314                 return menu;
    315             }
    316         }, true);
    317         addWidget(hostsTableDecorator, "job_hosts_table");
    318 
    319         abortButton.addClickHandler(new ClickHandler() {
    320             public void onClick(ClickEvent event) {
    321                 abortJob();
    322             }
    323         });
    324         addWidget(abortButton, "view_abort");
    325 
    326         cloneButton.addClickHandler(new ClickHandler() {
    327             public void onClick(ClickEvent event) {
    328                 cloneJob();
    329             }
    330         });
    331         addWidget(cloneButton, "view_clone");
    332 
    333         tkoResultsFrame.getElement().setAttribute("scrolling", "no");
    334         addWidget(tkoResultsFrame, "tko_results");
    335 
    336         controlFile.addStyleName("code");
    337         controlFilePanel.setContent(controlFile);
    338         addWidget(controlFilePanel, "view_control_file");
    339 
    340         if (!staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) {
    341             AfeUtils.removeElement("view_drone_set_wrapper");
    342         }
    343 
    344         getJobHistoryButton.addClickHandler(new ClickHandler() {
    345             public void onClick(ClickEvent event) {
    346                 getJobHistory();
    347             }
    348         });
    349         addWidget(getJobHistoryButton, "view_get_job_history");
    350 
    351         jobHistoryTable.setClickable(true);
    352         jobHistoryTable.addListener(new DataTableListener() {
    353             public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
    354                 String log_url= row.get("log_url").isString().stringValue();
    355                 jobHistoryListener.onJobSelected(log_url);
    356             }
    357 
    358             public void onTableRefreshed() {}
    359         });
    360         addWidget(jobHistoryTable, "job_history_table");
    361     }
    362 
    363     protected void addTableFilters() {
    364         hostsTable.addFilter(jobFilter);
    365         childJobsTable.addFilter(parentJobIdFliter);
    366 
    367         SearchFilter hostnameFilter = new SearchFilter("host__hostname", true);
    368         ListFilter statusFilter = new ListFilter("status");
    369         StaticDataRepository staticData = StaticDataRepository.getRepository();
    370         JSONArray statuses = staticData.getData("job_statuses").isArray();
    371         statusFilter.setChoices(Utils.JSONtoStrings(statuses));
    372 
    373         hostsTableDecorator.addFilter("Hostname", hostnameFilter);
    374         hostsTableDecorator.addFilter("Status", statusFilter);
    375     }
    376 
    377     private void abortJob() {
    378         JSONObject params = new JSONObject();
    379         params.put("job__id", new JSONNumber(jobId));
    380         AfeUtils.callAbort(params, new SimpleCallback() {
    381             public void doCallback(Object source) {
    382                 refresh();
    383             }
    384         });
    385     }
    386 
    387     private void abortSelectedHosts() {
    388         AfeUtils.abortHostQueueEntries(hostsSelectionManager.getSelectedObjects(),
    389                                        new SimpleCallback() {
    390             public void doCallback(Object source) {
    391                 refresh();
    392             }
    393         });
    394     }
    395 
    396     protected void cloneJob() {
    397         ContextMenu menu = new ContextMenu();
    398         menu.addItem("Reuse any similar hosts  (default)", new Command() {
    399             public void execute() {
    400                 cloneJob(false);
    401             }
    402         });
    403         menu.addItem("Reuse same specific hosts", new Command() {
    404             public void execute() {
    405                 cloneJob(true);
    406             }
    407         });
    408         menu.addItem("Use failed and aborted hosts", new Command() {
    409             public void execute() {
    410                 JSONObject queueEntryFilterData = new JSONObject();
    411                 String sql = "(status = 'Failed' OR aborted = TRUE OR " +
    412                              "(host_id IS NULL AND meta_host IS NULL))";
    413 
    414                 queueEntryFilterData.put("extra_where", new JSONString(sql));
    415                 cloneJob(true, queueEntryFilterData);
    416             }
    417         });
    418 
    419         menu.showAt(cloneButton.getAbsoluteLeft(),
    420                 cloneButton.getAbsoluteTop() + cloneButton.getOffsetHeight());
    421     }
    422 
    423     private void cloneJobOnSelectedHosts() {
    424         Set<JSONObject> hostsQueueEntries = hostsSelectionManager.getSelectedObjects();
    425         JSONArray queueEntryIds = new JSONArray();
    426         for (JSONObject queueEntry : hostsQueueEntries) {
    427           queueEntryIds.set(queueEntryIds.size(), queueEntry.get("id"));
    428         }
    429 
    430         JSONObject queueEntryFilterData = new JSONObject();
    431         queueEntryFilterData.put("id__in", queueEntryIds);
    432         cloneJob(true, queueEntryFilterData);
    433     }
    434 
    435     private void cloneJob(boolean preserveMetahosts) {
    436         cloneJob(preserveMetahosts, new JSONObject());
    437     }
    438 
    439     private void cloneJob(boolean preserveMetahosts, JSONObject queueEntryFilterData) {
    440         JSONObject params = new JSONObject();
    441         params.put("id", new JSONNumber(jobId));
    442         params.put("preserve_metahosts", JSONBoolean.getInstance(preserveMetahosts));
    443         params.put("queue_entry_filter_data", queueEntryFilterData);
    444 
    445         rpcProxy.rpcCall("get_info_for_clone", params, new JsonRpcCallback() {
    446             @Override
    447             public void onSuccess(JSONValue result) {
    448                 listener.onCloneJob(result);
    449             }
    450         });
    451     }
    452 
    453     private String getResultsURL(int jobId) {
    454         return "/new_tko/#tab_id=spreadsheet_view&row=hostname&column=test_name&" +
    455                "condition=afe_job_id+%253d+" + Integer.toString(jobId) + "&" +
    456                "show_incomplete=true";
    457     }
    458 
    459     private String getOldResultsUrl(int jobId) {
    460         return "/tko/compose_query.cgi?" +
    461                "columns=test&rows=hostname&condition=tag%7E%27" +
    462                Integer.toString(jobId) + "-%25%27&title=Report";
    463     }
    464 
    465     private String getTriageUrl(int jobId) {
    466         /*
    467          * Having a hard-coded path like this is very unfortunate, but there's no simple way
    468          * in the current design to generate this link by code.
    469          *
    470          * TODO: Redesign the system so that we can generate these links by code.
    471          *
    472          * Idea: Be able to instantiate a TableView object, ask it to set up to triage this job ID,
    473          *       and then ask it for the history URL.
    474          */
    475 
    476         return "/new_tko/#tab_id=table_view&columns=test_name%252Cstatus%252Cgroup_count%252C" +
    477                "reason&sort=test_name%252Cstatus%252Creason&condition=afe_job_id+%253D+" + jobId +
    478                "+AND+status+%253C%253E+%2527GOOD%2527&show_invalid=false";
    479     }
    480 
    481     private String getEmbeddedUrl(int jobId) {
    482         return "/embedded_spreadsheet/EmbeddedSpreadsheetClient.html?afe_job_id=" + jobId;
    483     }
    484 
    485     /**
    486      * Get the path for a job's raw result files.
    487      * @param jobLogsId id-owner, e.g. "172-showard"
    488      */
    489     protected String getLogsURL(String jobLogsId) {
    490         return Utils.getRetrieveLogsUrl(jobLogsId);
    491     }
    492 
    493     protected void pointToResults(String resultsUrl, String logsUrl,
    494                                   String oldResultsUrl, String triageUrl,
    495                                   String embeddedUrl) {
    496         getElementById("results_link").setAttribute("href", resultsUrl);
    497         getElementById("old_results_link").setAttribute("href", oldResultsUrl);
    498         getElementById("raw_results_link").setAttribute("href", logsUrl);
    499         getElementById("triage_failures_link").setAttribute("href", triageUrl);
    500 
    501         tkoResultsFrame.setSize(RESULTS_MAX_WIDTH, RESULTS_MAX_HEIGHT);
    502         if (!resultsUrl.equals(NO_URL)) {
    503             updateResultsFrame(tkoResultsFrame.getElement(), embeddedUrl);
    504         }
    505     }
    506 
    507     private native void updateResultsFrame(Element frame, String embeddedUrl) /*-{
    508         // Use location.replace() here so that the frame's URL changes don't show up in the browser
    509         // window's history
    510         frame.contentWindow.location.replace(embeddedUrl);
    511     }-*/;
    512 
    513     @Override
    514     protected String getNoObjectText() {
    515         return "No job selected";
    516     }
    517 
    518     @Override
    519     protected String getFetchControlsElementId() {
    520         return "job_id_fetch_controls";
    521     }
    522 
    523     @Override
    524     protected String getDataElementId() {
    525         return "view_data";
    526     }
    527 
    528     @Override
    529     protected String getTitleElementId() {
    530         return "view_title";
    531     }
    532 
    533     @Override
    534     protected String getObjectId() {
    535         if (jobId == NO_JOB_ID) {
    536             return NO_OBJECT;
    537         }
    538         return Integer.toString(jobId);
    539     }
    540 
    541     @Override
    542     public String getElementId() {
    543         return "view_job";
    544     }
    545 
    546     @Override
    547     protected void setObjectId(String id) {
    548         int newJobId;
    549         try {
    550             newJobId = Integer.parseInt(id);
    551         }
    552         catch (NumberFormatException exc) {
    553             throw new IllegalArgumentException();
    554         }
    555         this.jobId = newJobId;
    556     }
    557 
    558     public Widget createWidget(int row, int cell, JSONObject hostQueueEntry) {
    559         if (cell == 0) {
    560             return hostsSelectionManager.createWidget(row, cell, hostQueueEntry);
    561         }
    562 
    563         String executionSubdir = Utils.jsonToString(hostQueueEntry.get("execution_subdir"));
    564         if (executionSubdir.equals("")) {
    565             // when executionSubdir == "", it's a job that hasn't yet run.
    566             return null;
    567         }
    568 
    569         JSONObject jobObject = hostQueueEntry.get("job").isObject();
    570         String owner = Utils.jsonToString(jobObject.get("owner"));
    571         String basePath = jobId + "-" + owner + "/" + executionSubdir + "/";
    572 
    573         if (cell == JOB_HOSTS_COLUMNS.length - 1) {
    574             return new HTML(getLogsLinkHtml(basePath + "debug", "Debug logs"));
    575         } else {
    576             return new HTML(getLogsLinkHtml(basePath, "All logs"));
    577         }
    578     }
    579 
    580     private String getLogsLinkHtml(String url, String text) {
    581         url = Utils.getRetrieveLogsUrl(url);
    582         return "<a target=\"_blank\" href=\"" + url + "\">" + text + "</a>";
    583     }
    584 
    585     private void getJobHistory() {
    586         JSONObject params = new JSONObject();
    587         params.put("job_id", new JSONNumber(jobId));
    588         AfeUtils.callGetJobHistory(params, new SimpleCallback() {
    589             public void doCallback(Object result) {
    590                 jobHistoryTable.clear();
    591                 List<JSONObject> rows = new ArrayList<JSONObject>();
    592                 JSONArray history = (JSONArray)result;
    593                 for (int i = 0; i < history.size(); i++)
    594                     rows.add((JSONObject)history.get(i));
    595                 jobHistoryTable.addRows(rows);
    596             }
    597         }, true);
    598     }
    599 
    600     private void getSpongeUrl(JSONObject jobObject) {
    601         getElementById("view_sponge_invocation").setInnerHTML("");
    602         getElementById("view_sponge_invocation_wrapper").getStyle().setProperty("display", "none");
    603         JSONObject params = new JSONObject();
    604         params.put("afe_job_id", new JSONNumber(jobId));
    605         AfeUtils.callGetSpongeUrl(params, new SimpleCallback() {
    606             public void doCallback(Object spongeUrl) {
    607                 if (spongeUrl != null){
    608                     getElementById("view_sponge_invocation_wrapper").getStyle().setProperty("display", "");
    609                     getElementById("view_sponge_invocation").setAttribute("href", spongeUrl.toString());
    610                     getElementById("view_sponge_invocation").setInnerHTML(spongeUrl.toString());
    611                 }
    612             }
    613         });
    614     }
    615 }
    616