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 status log 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); onCreateRecurringJob(int id)72 public void onCreateRecurringJob(int id); 73 } 74 75 protected class ChildJobsListener { onJobSelected(int id)76 public void onJobSelected(int id) { 77 fetchById(Integer.toString(id)); 78 } 79 } 80 81 protected class JobHistoryListener { onJobSelected(String url)82 public void onJobSelected(String url) { 83 Utils.openUrlInNewWindow(url); 84 } 85 } 86 87 protected int jobId = NO_JOB_ID; 88 89 private JobStatusDataSource jobStatusDataSource = new JobStatusDataSource(); 90 protected JobTable childJobsTable = new JobTable(CHILD_JOBS_COLUMNS); 91 protected TableDecorator childJobsTableDecorator = new TableDecorator(childJobsTable); 92 protected SimpleFilter parentJobIdFliter = new SimpleFilter(); 93 protected DynamicTable hostsTable = new DynamicTable(JOB_HOSTS_COLUMNS, jobStatusDataSource); 94 protected TableDecorator hostsTableDecorator = new TableDecorator(hostsTable); 95 protected SimpleFilter jobFilter = new SimpleFilter(); 96 protected Button abortButton = new Button("Abort job"); 97 protected Button cloneButton = new Button("Clone job"); 98 protected Button recurringButton = new Button("Create recurring job"); 99 protected Frame tkoResultsFrame = new Frame(); 100 101 protected JobDetailListener listener; 102 protected ChildJobsListener childJobsListener = new ChildJobsListener(); 103 private SelectionManager hostsSelectionManager; 104 private SelectionManager childJobsSelectionManager; 105 106 private Label controlFile = new Label(); 107 private DisclosurePanel controlFilePanel = new DisclosurePanel(""); 108 109 protected StaticDataRepository staticData = StaticDataRepository.getRepository(); 110 111 protected Button getJobHistoryButton = new Button("Get Job History"); 112 protected JobHistoryListener jobHistoryListener = new JobHistoryListener(); 113 protected DataTable jobHistoryTable = new DataTable(JOB_HISTORY_COLUMNS); 114 JobDetailView(JobDetailListener listener)115 public JobDetailView(JobDetailListener listener) { 116 this.listener = listener; 117 setupSpreadsheetListener(Utils.getBaseUrl()); 118 } 119 setupSpreadsheetListener(String baseUrl)120 private native void setupSpreadsheetListener(String baseUrl) /*-{ 121 var ins = this; 122 $wnd.onSpreadsheetLoad = function(event) { 123 if (event.origin !== baseUrl) { 124 return; 125 } 126 ins.@autotest.afe.JobDetailView::resizeResultsFrame(Ljava/lang/String;)(event.data); 127 } 128 129 $wnd.addEventListener("message", $wnd.onSpreadsheetLoad, false); 130 }-*/; 131 132 @SuppressWarnings("unused") // called from native resizeResultsFrame(String message)133 private void resizeResultsFrame(String message) { 134 String[] parts = message.split(" "); 135 tkoResultsFrame.setSize(parts[0], parts[1]); 136 } 137 138 @Override fetchData()139 protected void fetchData() { 140 pointToResults(NO_URL, NO_URL, NO_URL, NO_URL, NO_URL); 141 JSONObject params = new JSONObject(); 142 params.put("id", new JSONNumber(jobId)); 143 rpcProxy.rpcCall("get_jobs_summary", params, new JsonRpcCallback() { 144 @Override 145 public void onSuccess(JSONValue result) { 146 JSONObject jobObject; 147 try { 148 jobObject = Utils.getSingleObjectFromArray(result.isArray()); 149 } 150 catch (IllegalArgumentException exc) { 151 NotifyManager.getInstance().showError("No such job found"); 152 resetPage(); 153 return; 154 } 155 String name = Utils.jsonToString(jobObject.get("name")); 156 String runVerify = Utils.jsonToString(jobObject.get("run_verify")); 157 158 showText(name, "view_label"); 159 showField(jobObject, "owner", "view_owner"); 160 String parent_job_url = Utils.jsonToString(jobObject.get("parent_job")).trim(); 161 if (parent_job_url.equals("<null>")){ 162 parent_job_url = "http://www.youtube.com/watch?v=oHg5SJYRHA0"; 163 } else { 164 parent_job_url = "#tab_id=view_job&object_id=" + parent_job_url; 165 } 166 showField(jobObject, "parent_job", "view_parent"); 167 getElementById("view_parent").setAttribute("href", parent_job_url); 168 showField(jobObject, "test_retry", "view_test_retry"); 169 double priorityValue = jobObject.get("priority").isNumber().getValue(); 170 String priorityName = staticData.getPriorityName(priorityValue); 171 showText(priorityName, "view_priority"); 172 showField(jobObject, "created_on", "view_created"); 173 showField(jobObject, "timeout_mins", "view_timeout"); 174 String imageUrlString = ""; 175 if (jobObject.containsKey("image")) { 176 imageUrlString = Utils.jsonToString(jobObject.get("image")).trim(); 177 } 178 showText(imageUrlString, "view_image_url"); 179 showField(jobObject, "max_runtime_mins", "view_max_runtime"); 180 showField(jobObject, "email_list", "view_email_list"); 181 showText(runVerify, "view_run_verify"); 182 showField(jobObject, "reboot_before", "view_reboot_before"); 183 showField(jobObject, "reboot_after", "view_reboot_after"); 184 showField(jobObject, "parse_failed_repair", "view_parse_failed_repair"); 185 showField(jobObject, "synch_count", "view_synch_count"); 186 if (jobObject.get("require_ssp").isNull() != null) 187 showText("false", "view_require_ssp"); 188 else { 189 String require_ssp = Utils.jsonToString(jobObject.get("require_ssp")); 190 showText(require_ssp, "view_require_ssp"); 191 } 192 showField(jobObject, "dependencies", "view_dependencies"); 193 194 if (staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) { 195 showField(jobObject, "drone_set", "view_drone_set"); 196 } 197 198 String header = Utils.jsonToString(jobObject.get("control_type")) + " control file"; 199 controlFilePanel.getHeaderTextAccessor().setText(header); 200 controlFile.setText(Utils.jsonToString(jobObject.get("control_file"))); 201 202 JSONObject counts = jobObject.get("status_counts").isObject(); 203 String countString = AfeUtils.formatStatusCounts(counts, ", "); 204 showText(countString, "view_status"); 205 abortButton.setVisible(isAnyEntryAbortable(counts)); 206 207 String shard_url = Utils.jsonToString(jobObject.get("shard")).trim(); 208 String job_id = Utils.jsonToString(jobObject.get("id")).trim(); 209 if (shard_url.equals("<null>")){ 210 shard_url = ""; 211 } else { 212 shard_url = "http://" + shard_url; 213 } 214 shard_url = shard_url + "/afe/#tab_id=view_job&object_id=" + job_id; 215 showField(jobObject, "shard", "view_job_on_shard"); 216 getElementById("view_job_on_shard").setAttribute("href", shard_url); 217 getElementById("view_job_on_shard").setInnerHTML(shard_url); 218 219 String jobTag = AfeUtils.getJobTag(jobObject); 220 pointToResults(getResultsURL(jobId), getLogsURL(jobTag), 221 getOldResultsUrl(jobId), getTriageUrl(jobId), 222 getEmbeddedUrl(jobId)); 223 224 String jobTitle = "Job: " + name + " (" + jobTag + ")"; 225 displayObjectData(jobTitle); 226 227 jobFilter.setParameter("job", new JSONNumber(jobId)); 228 hostsTable.refresh(); 229 230 parentJobIdFliter.setParameter("parent_job", new JSONNumber(jobId)); 231 childJobsTable.refresh(); 232 233 jobHistoryTable.clear(); 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 recurringButton.addClickHandler(new ClickHandler() { 334 public void onClick(ClickEvent event) { 335 createRecurringJob(); 336 } 337 }); 338 addWidget(recurringButton, "view_recurring"); 339 340 tkoResultsFrame.getElement().setAttribute("scrolling", "no"); 341 addWidget(tkoResultsFrame, "tko_results"); 342 343 controlFile.addStyleName("code"); 344 controlFilePanel.setContent(controlFile); 345 addWidget(controlFilePanel, "view_control_file"); 346 347 if (!staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) { 348 AfeUtils.removeElement("view_drone_set_wrapper"); 349 } 350 351 getJobHistoryButton.addClickHandler(new ClickHandler() { 352 public void onClick(ClickEvent event) { 353 getJobHistory(); 354 } 355 }); 356 addWidget(getJobHistoryButton, "view_get_job_history"); 357 358 jobHistoryTable.setClickable(true); 359 jobHistoryTable.addListener(new DataTableListener() { 360 public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { 361 String log_url= row.get("log_url").isString().stringValue(); 362 jobHistoryListener.onJobSelected(log_url); 363 } 364 365 public void onTableRefreshed() {} 366 }); 367 addWidget(jobHistoryTable, "job_history_table"); 368 } 369 addTableFilters()370 protected void addTableFilters() { 371 hostsTable.addFilter(jobFilter); 372 childJobsTable.addFilter(parentJobIdFliter); 373 374 SearchFilter hostnameFilter = new SearchFilter("host__hostname", true); 375 ListFilter statusFilter = new ListFilter("status"); 376 StaticDataRepository staticData = StaticDataRepository.getRepository(); 377 JSONArray statuses = staticData.getData("job_statuses").isArray(); 378 statusFilter.setChoices(Utils.JSONtoStrings(statuses)); 379 380 hostsTableDecorator.addFilter("Hostname", hostnameFilter); 381 hostsTableDecorator.addFilter("Status", statusFilter); 382 } 383 abortJob()384 private void abortJob() { 385 JSONObject params = new JSONObject(); 386 params.put("job__id", new JSONNumber(jobId)); 387 AfeUtils.callAbort(params, new SimpleCallback() { 388 public void doCallback(Object source) { 389 refresh(); 390 } 391 }); 392 } 393 abortSelectedHosts()394 private void abortSelectedHosts() { 395 AfeUtils.abortHostQueueEntries(hostsSelectionManager.getSelectedObjects(), 396 new SimpleCallback() { 397 public void doCallback(Object source) { 398 refresh(); 399 } 400 }); 401 } 402 cloneJob()403 protected void cloneJob() { 404 ContextMenu menu = new ContextMenu(); 405 menu.addItem("Reuse any similar hosts (default)", new Command() { 406 public void execute() { 407 cloneJob(false); 408 } 409 }); 410 menu.addItem("Reuse same specific hosts", new Command() { 411 public void execute() { 412 cloneJob(true); 413 } 414 }); 415 menu.addItem("Use failed and aborted hosts", new Command() { 416 public void execute() { 417 JSONObject queueEntryFilterData = new JSONObject(); 418 String sql = "(status = 'Failed' OR aborted = TRUE OR " + 419 "(host_id IS NULL AND meta_host IS NULL))"; 420 421 queueEntryFilterData.put("extra_where", new JSONString(sql)); 422 cloneJob(true, queueEntryFilterData); 423 } 424 }); 425 426 menu.showAt(cloneButton.getAbsoluteLeft(), 427 cloneButton.getAbsoluteTop() + cloneButton.getOffsetHeight()); 428 } 429 cloneJobOnSelectedHosts()430 private void cloneJobOnSelectedHosts() { 431 Set<JSONObject> hostsQueueEntries = hostsSelectionManager.getSelectedObjects(); 432 JSONArray queueEntryIds = new JSONArray(); 433 for (JSONObject queueEntry : hostsQueueEntries) { 434 queueEntryIds.set(queueEntryIds.size(), queueEntry.get("id")); 435 } 436 437 JSONObject queueEntryFilterData = new JSONObject(); 438 queueEntryFilterData.put("id__in", queueEntryIds); 439 cloneJob(true, queueEntryFilterData); 440 } 441 cloneJob(boolean preserveMetahosts)442 private void cloneJob(boolean preserveMetahosts) { 443 cloneJob(preserveMetahosts, new JSONObject()); 444 } 445 cloneJob(boolean preserveMetahosts, JSONObject queueEntryFilterData)446 private void cloneJob(boolean preserveMetahosts, JSONObject queueEntryFilterData) { 447 JSONObject params = new JSONObject(); 448 params.put("id", new JSONNumber(jobId)); 449 params.put("preserve_metahosts", JSONBoolean.getInstance(preserveMetahosts)); 450 params.put("queue_entry_filter_data", queueEntryFilterData); 451 452 rpcProxy.rpcCall("get_info_for_clone", params, new JsonRpcCallback() { 453 @Override 454 public void onSuccess(JSONValue result) { 455 listener.onCloneJob(result); 456 } 457 }); 458 } 459 createRecurringJob()460 private void createRecurringJob() { 461 listener.onCreateRecurringJob(jobId); 462 } 463 getResultsURL(int jobId)464 private String getResultsURL(int jobId) { 465 return "/new_tko/#tab_id=spreadsheet_view&row=hostname&column=test_name&" + 466 "condition=afe_job_id+%253d+" + Integer.toString(jobId) + "&" + 467 "show_incomplete=true"; 468 } 469 getOldResultsUrl(int jobId)470 private String getOldResultsUrl(int jobId) { 471 return "/tko/compose_query.cgi?" + 472 "columns=test&rows=hostname&condition=tag%7E%27" + 473 Integer.toString(jobId) + "-%25%27&title=Report"; 474 } 475 getTriageUrl(int jobId)476 private String getTriageUrl(int jobId) { 477 /* 478 * Having a hard-coded path like this is very unfortunate, but there's no simple way 479 * in the current design to generate this link by code. 480 * 481 * TODO: Redesign the system so that we can generate these links by code. 482 * 483 * Idea: Be able to instantiate a TableView object, ask it to set up to triage this job ID, 484 * and then ask it for the history URL. 485 */ 486 487 return "/new_tko/#tab_id=table_view&columns=test_name%252Cstatus%252Cgroup_count%252C" + 488 "reason&sort=test_name%252Cstatus%252Creason&condition=afe_job_id+%253D+" + jobId + 489 "+AND+status+%253C%253E+%2527GOOD%2527&show_invalid=false"; 490 } 491 getEmbeddedUrl(int jobId)492 private String getEmbeddedUrl(int jobId) { 493 return "/embedded_spreadsheet/EmbeddedSpreadsheetClient.html?afe_job_id=" + jobId; 494 } 495 496 /** 497 * Get the path for a job's raw result files. 498 * @param jobLogsId id-owner, e.g. "172-showard" 499 */ getLogsURL(String jobLogsId)500 protected String getLogsURL(String jobLogsId) { 501 return Utils.getRetrieveLogsUrl(jobLogsId); 502 } 503 pointToResults(String resultsUrl, String logsUrl, String oldResultsUrl, String triageUrl, String embeddedUrl)504 protected void pointToResults(String resultsUrl, String logsUrl, 505 String oldResultsUrl, String triageUrl, 506 String embeddedUrl) { 507 getElementById("results_link").setAttribute("href", resultsUrl); 508 getElementById("old_results_link").setAttribute("href", oldResultsUrl); 509 getElementById("raw_results_link").setAttribute("href", logsUrl); 510 getElementById("triage_failures_link").setAttribute("href", triageUrl); 511 512 tkoResultsFrame.setSize(RESULTS_MAX_WIDTH, RESULTS_MAX_HEIGHT); 513 if (!resultsUrl.equals(NO_URL)) { 514 updateResultsFrame(tkoResultsFrame.getElement(), embeddedUrl); 515 } 516 } 517 updateResultsFrame(Element frame, String embeddedUrl)518 private native void updateResultsFrame(Element frame, String embeddedUrl) /*-{ 519 // Use location.replace() here so that the frame's URL changes don't show up in the browser 520 // window's history 521 frame.contentWindow.location.replace(embeddedUrl); 522 }-*/; 523 524 @Override getNoObjectText()525 protected String getNoObjectText() { 526 return "No job selected"; 527 } 528 529 @Override getFetchControlsElementId()530 protected String getFetchControlsElementId() { 531 return "job_id_fetch_controls"; 532 } 533 534 @Override getDataElementId()535 protected String getDataElementId() { 536 return "view_data"; 537 } 538 539 @Override getTitleElementId()540 protected String getTitleElementId() { 541 return "view_title"; 542 } 543 544 @Override getObjectId()545 protected String getObjectId() { 546 if (jobId == NO_JOB_ID) { 547 return NO_OBJECT; 548 } 549 return Integer.toString(jobId); 550 } 551 552 @Override getElementId()553 public String getElementId() { 554 return "view_job"; 555 } 556 557 @Override setObjectId(String id)558 protected void setObjectId(String id) { 559 int newJobId; 560 try { 561 newJobId = Integer.parseInt(id); 562 } 563 catch (NumberFormatException exc) { 564 throw new IllegalArgumentException(); 565 } 566 this.jobId = newJobId; 567 } 568 createWidget(int row, int cell, JSONObject hostQueueEntry)569 public Widget createWidget(int row, int cell, JSONObject hostQueueEntry) { 570 if (cell == 0) { 571 return hostsSelectionManager.createWidget(row, cell, hostQueueEntry); 572 } 573 574 String executionSubdir = Utils.jsonToString(hostQueueEntry.get("execution_subdir")); 575 if (executionSubdir.equals("")) { 576 // when executionSubdir == "", it's a job that hasn't yet run. 577 return null; 578 } 579 580 JSONObject jobObject = hostQueueEntry.get("job").isObject(); 581 String owner = Utils.jsonToString(jobObject.get("owner")); 582 String basePath = jobId + "-" + owner + "/" + executionSubdir + "/"; 583 584 if (cell == JOB_HOSTS_COLUMNS.length - 1) { 585 return new HTML(getLogsLinkHtml(basePath + "debug", "Debug logs")); 586 } else { 587 return new HTML(getLogsLinkHtml(basePath + "status.log", "Status log")); 588 } 589 } 590 getLogsLinkHtml(String url, String text)591 private String getLogsLinkHtml(String url, String text) { 592 url = Utils.getRetrieveLogsUrl(url); 593 return "<a target=\"_blank\" href=\"" + url + "\">" + text + "</a>"; 594 } 595 getJobHistory()596 private void getJobHistory() { 597 JSONObject params = new JSONObject(); 598 params.put("job_id", new JSONNumber(jobId)); 599 AfeUtils.callGetJobHistory(params, new SimpleCallback() { 600 public void doCallback(Object result) { 601 jobHistoryTable.clear(); 602 List<JSONObject> rows = new ArrayList<JSONObject>(); 603 JSONArray history = (JSONArray)result; 604 for (int i = 0; i < history.size(); i++) 605 rows.add((JSONObject)history.get(i)); 606 jobHistoryTable.addRows(rows); 607 } 608 }, true); 609 } 610 } 611