• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onHostSelected(String hostId)70         public void onHostSelected(String hostId);
onCloneJob(JSONValue result)71         public void onCloneJob(JSONValue result);
72     }
73 
74     protected class ChildJobsListener {
onJobSelected(int id)75         public void onJobSelected(int id) {
76             fetchById(Integer.toString(id));
77         }
78     }
79 
80     protected class JobHistoryListener {
onJobSelected(String url)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 
JobDetailView(JobDetailListener listener)113     public JobDetailView(JobDetailListener listener) {
114         this.listener = listener;
115         setupSpreadsheetListener(Utils.getBaseUrl());
116     }
117 
setupSpreadsheetListener(String baseUrl)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.@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
resizeResultsFrame(String message)131     private void resizeResultsFrame(String message) {
132         String[] parts = message.split(" ");
133         tkoResultsFrame.setSize(parts[0], parts[1]);
134     }
135 
136     @Override
fetchData()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 
isAnyEntryAbortable(JSONObject statusCounts)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
initialize()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 
addTableFilters()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 
abortJob()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 
abortSelectedHosts()387     private void abortSelectedHosts() {
388         AfeUtils.abortHostQueueEntries(hostsSelectionManager.getSelectedObjects(),
389                                        new SimpleCallback() {
390             public void doCallback(Object source) {
391                 refresh();
392             }
393         });
394     }
395 
cloneJob()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 
cloneJobOnSelectedHosts()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 
cloneJob(boolean preserveMetahosts)435     private void cloneJob(boolean preserveMetahosts) {
436         cloneJob(preserveMetahosts, new JSONObject());
437     }
438 
cloneJob(boolean preserveMetahosts, JSONObject queueEntryFilterData)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 
getResultsURL(int jobId)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 
getOldResultsUrl(int jobId)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 
getTriageUrl(int jobId)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 
getEmbeddedUrl(int jobId)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      */
getLogsURL(String jobLogsId)489     protected String getLogsURL(String jobLogsId) {
490         return Utils.getRetrieveLogsUrl(jobLogsId);
491     }
492 
pointToResults(String resultsUrl, String logsUrl, String oldResultsUrl, String triageUrl, String embeddedUrl)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 
updateResultsFrame(Element frame, String embeddedUrl)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
getNoObjectText()514     protected String getNoObjectText() {
515         return "No job selected";
516     }
517 
518     @Override
getFetchControlsElementId()519     protected String getFetchControlsElementId() {
520         return "job_id_fetch_controls";
521     }
522 
523     @Override
getDataElementId()524     protected String getDataElementId() {
525         return "view_data";
526     }
527 
528     @Override
getTitleElementId()529     protected String getTitleElementId() {
530         return "view_title";
531     }
532 
533     @Override
getObjectId()534     protected String getObjectId() {
535         if (jobId == NO_JOB_ID) {
536             return NO_OBJECT;
537         }
538         return Integer.toString(jobId);
539     }
540 
541     @Override
getElementId()542     public String getElementId() {
543         return "view_job";
544     }
545 
546     @Override
setObjectId(String id)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 
createWidget(int row, int cell, JSONObject hostQueueEntry)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 
getLogsLinkHtml(String url, String text)580     private String getLogsLinkHtml(String url, String text) {
581         url = Utils.getRetrieveLogsUrl(url);
582         return "<a target=\"_blank\" href=\"" + url + "\">" + text + "</a>";
583     }
584 
getJobHistory()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 
getSpongeUrl(JSONObject jobObject)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