| package autotest.afe; |
| |
| import autotest.common.JsonRpcCallback; |
| import autotest.common.SimpleCallback; |
| import autotest.common.StaticDataRepository; |
| import autotest.common.Utils; |
| import autotest.common.table.DataTable; |
| import autotest.common.table.DataTable.DataTableListener; |
| import autotest.common.table.DynamicTable; |
| import autotest.common.table.ListFilter; |
| import autotest.common.table.RpcDataSource; |
| import autotest.common.table.SearchFilter; |
| import autotest.common.table.SelectionManager; |
| import autotest.common.table.SimpleFilter; |
| import autotest.common.table.TableDecorator; |
| import autotest.common.table.DataTable.TableWidgetFactory; |
| import autotest.common.table.DynamicTable.DynamicTableListener; |
| import autotest.common.ui.ContextMenu; |
| import autotest.common.ui.DetailView; |
| import autotest.common.ui.NotifyManager; |
| import autotest.common.ui.TableActionsPanel.TableActionsListener; |
| |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.json.client.JSONArray; |
| import com.google.gwt.json.client.JSONBoolean; |
| import com.google.gwt.json.client.JSONNumber; |
| import com.google.gwt.json.client.JSONObject; |
| import com.google.gwt.json.client.JSONString; |
| import com.google.gwt.json.client.JSONValue; |
| import com.google.gwt.user.client.Command; |
| import com.google.gwt.user.client.ui.Button; |
| import com.google.gwt.user.client.ui.DisclosurePanel; |
| import com.google.gwt.user.client.ui.Frame; |
| import com.google.gwt.user.client.ui.HTML; |
| import com.google.gwt.user.client.ui.Label; |
| import com.google.gwt.user.client.ui.Widget; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class JobDetailView extends DetailView implements TableWidgetFactory { |
| private static final String[][] JOB_HOSTS_COLUMNS = { |
| {DataTable.CLICKABLE_WIDGET_COLUMN, ""}, // selection checkbox |
| {"hostname", "Host"}, {"full_status", "Status"}, |
| {"host_status", "Host Status"}, {"host_locked", "Host Locked"}, |
| // columns for all logs and debug log links |
| {DataTable.CLICKABLE_WIDGET_COLUMN, ""}, {DataTable.CLICKABLE_WIDGET_COLUMN, ""} |
| }; |
| private static final String[][] CHILD_JOBS_COLUMNS = { |
| { "id", "ID" }, { "name", "Name" }, { "priority", "Priority" }, |
| { "control_type", "Client/Server" }, { JobTable.HOSTS_SUMMARY, "Status" }, |
| { JobTable.RESULTS_SUMMARY, "Passed Tests" } |
| }; |
| private static final String[][] JOB_HISTORY_COLUMNS = { |
| { "id", "ID" }, { "hostname", "Host" }, { "name", "Name" }, |
| { "start_time", "Start Time" }, { "end_time", "End Time" }, |
| { "time_used", "Time Used (seconds)" }, { "status", "Status" } |
| }; |
| public static final String NO_URL = "about:blank"; |
| public static final int NO_JOB_ID = -1; |
| public static final int HOSTS_PER_PAGE = 30; |
| public static final int CHILD_JOBS_PER_PAGE = 30; |
| public static final String RESULTS_MAX_WIDTH = "700px"; |
| public static final String RESULTS_MAX_HEIGHT = "500px"; |
| |
| public interface JobDetailListener { |
| public void onHostSelected(String hostId); |
| public void onCloneJob(JSONValue result); |
| } |
| |
| protected class ChildJobsListener { |
| public void onJobSelected(int id) { |
| fetchById(Integer.toString(id)); |
| } |
| } |
| |
| protected class JobHistoryListener { |
| public void onJobSelected(String url) { |
| Utils.openUrlInNewWindow(url); |
| } |
| } |
| |
| protected int jobId = NO_JOB_ID; |
| |
| private JobStatusDataSource jobStatusDataSource = new JobStatusDataSource(); |
| protected JobTable childJobsTable = new JobTable(CHILD_JOBS_COLUMNS); |
| protected TableDecorator childJobsTableDecorator = new TableDecorator(childJobsTable); |
| protected SimpleFilter parentJobIdFliter = new SimpleFilter(); |
| protected DynamicTable hostsTable = new DynamicTable(JOB_HOSTS_COLUMNS, jobStatusDataSource); |
| protected TableDecorator hostsTableDecorator = new TableDecorator(hostsTable); |
| protected SimpleFilter jobFilter = new SimpleFilter(); |
| protected Button abortButton = new Button("Abort job"); |
| protected Button cloneButton = new Button("Clone job"); |
| protected Frame tkoResultsFrame = new Frame(); |
| |
| protected JobDetailListener listener; |
| protected ChildJobsListener childJobsListener = new ChildJobsListener(); |
| private SelectionManager hostsSelectionManager; |
| private SelectionManager childJobsSelectionManager; |
| |
| private Label controlFile = new Label(); |
| private DisclosurePanel controlFilePanel = new DisclosurePanel(""); |
| |
| protected StaticDataRepository staticData = StaticDataRepository.getRepository(); |
| |
| protected Button getJobHistoryButton = new Button("Get Job History"); |
| protected JobHistoryListener jobHistoryListener = new JobHistoryListener(); |
| protected DataTable jobHistoryTable = new DataTable(JOB_HISTORY_COLUMNS); |
| |
| public JobDetailView(JobDetailListener listener) { |
| this.listener = listener; |
| setupSpreadsheetListener(Utils.getBaseUrl()); |
| } |
| |
| private native void setupSpreadsheetListener(String baseUrl) /*-{ |
| var ins = this; |
| $wnd.onSpreadsheetLoad = function(event) { |
| if (event.origin !== baseUrl) { |
| return; |
| } |
| ins.@autotest.afe.JobDetailView::resizeResultsFrame(Ljava/lang/String;)(event.data); |
| } |
| |
| $wnd.addEventListener("message", $wnd.onSpreadsheetLoad, false); |
| }-*/; |
| |
| @SuppressWarnings("unused") // called from native |
| private void resizeResultsFrame(String message) { |
| String[] parts = message.split(" "); |
| tkoResultsFrame.setSize(parts[0], parts[1]); |
| } |
| |
| @Override |
| protected void fetchData() { |
| pointToResults(NO_URL, NO_URL, NO_URL, NO_URL, NO_URL); |
| JSONObject params = new JSONObject(); |
| params.put("id", new JSONNumber(jobId)); |
| rpcProxy.rpcCall("get_jobs_summary", params, new JsonRpcCallback() { |
| @Override |
| public void onSuccess(JSONValue result) { |
| JSONObject jobObject; |
| try { |
| jobObject = Utils.getSingleObjectFromArray(result.isArray()); |
| } |
| catch (IllegalArgumentException exc) { |
| NotifyManager.getInstance().showError("No such job found"); |
| resetPage(); |
| return; |
| } |
| String name = Utils.jsonToString(jobObject.get("name")); |
| String runVerify = Utils.jsonToString(jobObject.get("run_verify")); |
| |
| showText(name, "view_label"); |
| showField(jobObject, "owner", "view_owner"); |
| String parent_job_url = Utils.jsonToString(jobObject.get("parent_job")).trim(); |
| if (parent_job_url.equals("<null>")){ |
| parent_job_url = "http://www.youtube.com/watch?v=oHg5SJYRHA0"; |
| } else { |
| parent_job_url = "#tab_id=view_job&object_id=" + parent_job_url; |
| } |
| showField(jobObject, "parent_job", "view_parent"); |
| getElementById("view_parent").setAttribute("href", parent_job_url); |
| showField(jobObject, "test_retry", "view_test_retry"); |
| double priorityValue = jobObject.get("priority").isNumber().getValue(); |
| String priorityName = staticData.getPriorityName(priorityValue); |
| showText(priorityName, "view_priority"); |
| showField(jobObject, "created_on", "view_created"); |
| showField(jobObject, "timeout_mins", "view_timeout"); |
| String imageUrlString = ""; |
| if (jobObject.containsKey("image")) { |
| imageUrlString = Utils.jsonToString(jobObject.get("image")).trim(); |
| } |
| showText(imageUrlString, "view_image_url"); |
| showField(jobObject, "max_runtime_mins", "view_max_runtime"); |
| showField(jobObject, "email_list", "view_email_list"); |
| showText(runVerify, "view_run_verify"); |
| showField(jobObject, "reboot_before", "view_reboot_before"); |
| showField(jobObject, "reboot_after", "view_reboot_after"); |
| showField(jobObject, "parse_failed_repair", "view_parse_failed_repair"); |
| showField(jobObject, "synch_count", "view_synch_count"); |
| if (jobObject.get("require_ssp").isNull() != null) |
| showText("false", "view_require_ssp"); |
| else { |
| String require_ssp = Utils.jsonToString(jobObject.get("require_ssp")); |
| showText(require_ssp, "view_require_ssp"); |
| } |
| showField(jobObject, "dependencies", "view_dependencies"); |
| |
| if (staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) { |
| showField(jobObject, "drone_set", "view_drone_set"); |
| } |
| |
| String header = Utils.jsonToString(jobObject.get("control_type")) + " control file"; |
| controlFilePanel.getHeaderTextAccessor().setText(header); |
| controlFile.setText(Utils.jsonToString(jobObject.get("control_file"))); |
| |
| JSONObject counts = jobObject.get("status_counts").isObject(); |
| String countString = AfeUtils.formatStatusCounts(counts, ", "); |
| showText(countString, "view_status"); |
| abortButton.setVisible(isAnyEntryAbortable(counts)); |
| |
| String shard_url = Utils.jsonToString(jobObject.get("shard")).trim(); |
| String job_id = Utils.jsonToString(jobObject.get("id")).trim(); |
| if (shard_url.equals("<null>")){ |
| shard_url = ""; |
| } else { |
| shard_url = "http://" + shard_url; |
| } |
| shard_url = shard_url + "/afe/#tab_id=view_job&object_id=" + job_id; |
| showField(jobObject, "shard", "view_job_on_shard"); |
| getElementById("view_job_on_shard").setAttribute("href", shard_url); |
| getElementById("view_job_on_shard").setInnerHTML(shard_url); |
| |
| String jobTag = AfeUtils.getJobTag(jobObject); |
| pointToResults(getResultsURL(jobId), getLogsURL(jobTag), |
| getOldResultsUrl(jobId), getTriageUrl(jobId), |
| getEmbeddedUrl(jobId)); |
| |
| String jobTitle = "Job: " + name + " (" + jobTag + ")"; |
| displayObjectData(jobTitle); |
| |
| jobFilter.setParameter("job", new JSONNumber(jobId)); |
| hostsTable.refresh(); |
| |
| parentJobIdFliter.setParameter("parent_job", new JSONNumber(jobId)); |
| childJobsTable.refresh(); |
| |
| jobHistoryTable.clear(); |
| |
| getSpongeUrl(jobObject); |
| } |
| |
| |
| @Override |
| public void onError(JSONObject errorObject) { |
| super.onError(errorObject); |
| resetPage(); |
| } |
| }); |
| } |
| |
| protected boolean isAnyEntryAbortable(JSONObject statusCounts) { |
| Set<String> statuses = statusCounts.keySet(); |
| for (String status : statuses) { |
| if (!(status.equals("Completed") || |
| status.equals("Failed") || |
| status.equals("Stopped") || |
| status.startsWith("Aborted"))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void initialize() { |
| super.initialize(); |
| |
| idInput.setVisibleLength(5); |
| |
| childJobsTable.setRowsPerPage(CHILD_JOBS_PER_PAGE); |
| childJobsTable.setClickable(true); |
| childJobsTable.addListener(new DynamicTableListener() { |
| public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { |
| int jobId = (int) row.get("id").isNumber().doubleValue(); |
| childJobsListener.onJobSelected(jobId); |
| } |
| |
| public void onTableRefreshed() {} |
| }); |
| |
| childJobsTableDecorator.addPaginators(); |
| childJobsSelectionManager = childJobsTableDecorator.addSelectionManager(false); |
| childJobsTable.setWidgetFactory(childJobsSelectionManager); |
| addWidget(childJobsTableDecorator, "child_jobs_table"); |
| |
| hostsTable.setRowsPerPage(HOSTS_PER_PAGE); |
| hostsTable.setClickable(true); |
| hostsTable.addListener(new DynamicTableListener() { |
| public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { |
| JSONObject host = row.get("host").isObject(); |
| String id = host.get("id").toString(); |
| listener.onHostSelected(id); |
| } |
| |
| public void onTableRefreshed() {} |
| }); |
| hostsTable.setWidgetFactory(this); |
| |
| hostsTableDecorator.addPaginators(); |
| addTableFilters(); |
| hostsSelectionManager = hostsTableDecorator.addSelectionManager(false); |
| hostsTableDecorator.addTableActionsPanel(new TableActionsListener() { |
| public ContextMenu getActionMenu() { |
| ContextMenu menu = new ContextMenu(); |
| |
| menu.addItem("Abort hosts", new Command() { |
| public void execute() { |
| abortSelectedHosts(); |
| } |
| }); |
| |
| menu.addItem("Clone job on selected hosts", new Command() { |
| public void execute() { |
| cloneJobOnSelectedHosts(); |
| } |
| }); |
| |
| if (hostsSelectionManager.isEmpty()) |
| menu.setEnabled(false); |
| return menu; |
| } |
| }, true); |
| addWidget(hostsTableDecorator, "job_hosts_table"); |
| |
| abortButton.addClickHandler(new ClickHandler() { |
| public void onClick(ClickEvent event) { |
| abortJob(); |
| } |
| }); |
| addWidget(abortButton, "view_abort"); |
| |
| cloneButton.addClickHandler(new ClickHandler() { |
| public void onClick(ClickEvent event) { |
| cloneJob(); |
| } |
| }); |
| addWidget(cloneButton, "view_clone"); |
| |
| tkoResultsFrame.getElement().setAttribute("scrolling", "no"); |
| addWidget(tkoResultsFrame, "tko_results"); |
| |
| controlFile.addStyleName("code"); |
| controlFilePanel.setContent(controlFile); |
| addWidget(controlFilePanel, "view_control_file"); |
| |
| if (!staticData.getData("drone_sets_enabled").isBoolean().booleanValue()) { |
| AfeUtils.removeElement("view_drone_set_wrapper"); |
| } |
| |
| getJobHistoryButton.addClickHandler(new ClickHandler() { |
| public void onClick(ClickEvent event) { |
| getJobHistory(); |
| } |
| }); |
| addWidget(getJobHistoryButton, "view_get_job_history"); |
| |
| jobHistoryTable.setClickable(true); |
| jobHistoryTable.addListener(new DataTableListener() { |
| public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) { |
| String log_url= row.get("log_url").isString().stringValue(); |
| jobHistoryListener.onJobSelected(log_url); |
| } |
| |
| public void onTableRefreshed() {} |
| }); |
| addWidget(jobHistoryTable, "job_history_table"); |
| } |
| |
| protected void addTableFilters() { |
| hostsTable.addFilter(jobFilter); |
| childJobsTable.addFilter(parentJobIdFliter); |
| |
| SearchFilter hostnameFilter = new SearchFilter("host__hostname", true); |
| ListFilter statusFilter = new ListFilter("status"); |
| StaticDataRepository staticData = StaticDataRepository.getRepository(); |
| JSONArray statuses = staticData.getData("job_statuses").isArray(); |
| statusFilter.setChoices(Utils.JSONtoStrings(statuses)); |
| |
| hostsTableDecorator.addFilter("Hostname", hostnameFilter); |
| hostsTableDecorator.addFilter("Status", statusFilter); |
| } |
| |
| private void abortJob() { |
| JSONObject params = new JSONObject(); |
| params.put("job__id", new JSONNumber(jobId)); |
| AfeUtils.callAbort(params, new SimpleCallback() { |
| public void doCallback(Object source) { |
| refresh(); |
| } |
| }); |
| } |
| |
| private void abortSelectedHosts() { |
| AfeUtils.abortHostQueueEntries(hostsSelectionManager.getSelectedObjects(), |
| new SimpleCallback() { |
| public void doCallback(Object source) { |
| refresh(); |
| } |
| }); |
| } |
| |
| protected void cloneJob() { |
| ContextMenu menu = new ContextMenu(); |
| menu.addItem("Reuse any similar hosts (default)", new Command() { |
| public void execute() { |
| cloneJob(false); |
| } |
| }); |
| menu.addItem("Reuse same specific hosts", new Command() { |
| public void execute() { |
| cloneJob(true); |
| } |
| }); |
| menu.addItem("Use failed and aborted hosts", new Command() { |
| public void execute() { |
| JSONObject queueEntryFilterData = new JSONObject(); |
| String sql = "(status = 'Failed' OR aborted = TRUE OR " + |
| "(host_id IS NULL AND meta_host IS NULL))"; |
| |
| queueEntryFilterData.put("extra_where", new JSONString(sql)); |
| cloneJob(true, queueEntryFilterData); |
| } |
| }); |
| |
| menu.showAt(cloneButton.getAbsoluteLeft(), |
| cloneButton.getAbsoluteTop() + cloneButton.getOffsetHeight()); |
| } |
| |
| private void cloneJobOnSelectedHosts() { |
| Set<JSONObject> hostsQueueEntries = hostsSelectionManager.getSelectedObjects(); |
| JSONArray queueEntryIds = new JSONArray(); |
| for (JSONObject queueEntry : hostsQueueEntries) { |
| queueEntryIds.set(queueEntryIds.size(), queueEntry.get("id")); |
| } |
| |
| JSONObject queueEntryFilterData = new JSONObject(); |
| queueEntryFilterData.put("id__in", queueEntryIds); |
| cloneJob(true, queueEntryFilterData); |
| } |
| |
| private void cloneJob(boolean preserveMetahosts) { |
| cloneJob(preserveMetahosts, new JSONObject()); |
| } |
| |
| private void cloneJob(boolean preserveMetahosts, JSONObject queueEntryFilterData) { |
| JSONObject params = new JSONObject(); |
| params.put("id", new JSONNumber(jobId)); |
| params.put("preserve_metahosts", JSONBoolean.getInstance(preserveMetahosts)); |
| params.put("queue_entry_filter_data", queueEntryFilterData); |
| |
| rpcProxy.rpcCall("get_info_for_clone", params, new JsonRpcCallback() { |
| @Override |
| public void onSuccess(JSONValue result) { |
| listener.onCloneJob(result); |
| } |
| }); |
| } |
| |
| private String getResultsURL(int jobId) { |
| return "/new_tko/#tab_id=spreadsheet_view&row=hostname&column=test_name&" + |
| "condition=afe_job_id+%253d+" + Integer.toString(jobId) + "&" + |
| "show_incomplete=true"; |
| } |
| |
| private String getOldResultsUrl(int jobId) { |
| return "/tko/compose_query.cgi?" + |
| "columns=test&rows=hostname&condition=tag%7E%27" + |
| Integer.toString(jobId) + "-%25%27&title=Report"; |
| } |
| |
| private String getTriageUrl(int jobId) { |
| /* |
| * Having a hard-coded path like this is very unfortunate, but there's no simple way |
| * in the current design to generate this link by code. |
| * |
| * TODO: Redesign the system so that we can generate these links by code. |
| * |
| * Idea: Be able to instantiate a TableView object, ask it to set up to triage this job ID, |
| * and then ask it for the history URL. |
| */ |
| |
| return "/new_tko/#tab_id=table_view&columns=test_name%252Cstatus%252Cgroup_count%252C" + |
| "reason&sort=test_name%252Cstatus%252Creason&condition=afe_job_id+%253D+" + jobId + |
| "+AND+status+%253C%253E+%2527GOOD%2527&show_invalid=false"; |
| } |
| |
| private String getEmbeddedUrl(int jobId) { |
| return "/embedded_spreadsheet/EmbeddedSpreadsheetClient.html?afe_job_id=" + jobId; |
| } |
| |
| /** |
| * Get the path for a job's raw result files. |
| * @param jobLogsId id-owner, e.g. "172-showard" |
| */ |
| protected String getLogsURL(String jobLogsId) { |
| return Utils.getRetrieveLogsUrl(jobLogsId); |
| } |
| |
| protected void pointToResults(String resultsUrl, String logsUrl, |
| String oldResultsUrl, String triageUrl, |
| String embeddedUrl) { |
| getElementById("results_link").setAttribute("href", resultsUrl); |
| getElementById("old_results_link").setAttribute("href", oldResultsUrl); |
| getElementById("raw_results_link").setAttribute("href", logsUrl); |
| getElementById("triage_failures_link").setAttribute("href", triageUrl); |
| |
| tkoResultsFrame.setSize(RESULTS_MAX_WIDTH, RESULTS_MAX_HEIGHT); |
| if (!resultsUrl.equals(NO_URL)) { |
| updateResultsFrame(tkoResultsFrame.getElement(), embeddedUrl); |
| } |
| } |
| |
| private native void updateResultsFrame(Element frame, String embeddedUrl) /*-{ |
| // Use location.replace() here so that the frame's URL changes don't show up in the browser |
| // window's history |
| frame.contentWindow.location.replace(embeddedUrl); |
| }-*/; |
| |
| @Override |
| protected String getNoObjectText() { |
| return "No job selected"; |
| } |
| |
| @Override |
| protected String getFetchControlsElementId() { |
| return "job_id_fetch_controls"; |
| } |
| |
| @Override |
| protected String getDataElementId() { |
| return "view_data"; |
| } |
| |
| @Override |
| protected String getTitleElementId() { |
| return "view_title"; |
| } |
| |
| @Override |
| protected String getObjectId() { |
| if (jobId == NO_JOB_ID) { |
| return NO_OBJECT; |
| } |
| return Integer.toString(jobId); |
| } |
| |
| @Override |
| public String getElementId() { |
| return "view_job"; |
| } |
| |
| @Override |
| protected void setObjectId(String id) { |
| int newJobId; |
| try { |
| newJobId = Integer.parseInt(id); |
| } |
| catch (NumberFormatException exc) { |
| throw new IllegalArgumentException(); |
| } |
| this.jobId = newJobId; |
| } |
| |
| public Widget createWidget(int row, int cell, JSONObject hostQueueEntry) { |
| if (cell == 0) { |
| return hostsSelectionManager.createWidget(row, cell, hostQueueEntry); |
| } |
| |
| String executionSubdir = Utils.jsonToString(hostQueueEntry.get("execution_subdir")); |
| if (executionSubdir.equals("")) { |
| // when executionSubdir == "", it's a job that hasn't yet run. |
| return null; |
| } |
| |
| JSONObject jobObject = hostQueueEntry.get("job").isObject(); |
| String owner = Utils.jsonToString(jobObject.get("owner")); |
| String basePath = jobId + "-" + owner + "/" + executionSubdir + "/"; |
| |
| if (cell == JOB_HOSTS_COLUMNS.length - 1) { |
| return new HTML(getLogsLinkHtml(basePath + "debug", "Debug logs")); |
| } else { |
| return new HTML(getLogsLinkHtml(basePath, "All logs")); |
| } |
| } |
| |
| private String getLogsLinkHtml(String url, String text) { |
| url = Utils.getRetrieveLogsUrl(url); |
| return "<a target=\"_blank\" href=\"" + url + "\">" + text + "</a>"; |
| } |
| |
| private void getJobHistory() { |
| JSONObject params = new JSONObject(); |
| params.put("job_id", new JSONNumber(jobId)); |
| AfeUtils.callGetJobHistory(params, new SimpleCallback() { |
| public void doCallback(Object result) { |
| jobHistoryTable.clear(); |
| List<JSONObject> rows = new ArrayList<JSONObject>(); |
| JSONArray history = (JSONArray)result; |
| for (int i = 0; i < history.size(); i++) |
| rows.add((JSONObject)history.get(i)); |
| jobHistoryTable.addRows(rows); |
| } |
| }, true); |
| } |
| |
| private void getSpongeUrl(JSONObject jobObject) { |
| getElementById("view_sponge_invocation").setInnerHTML(""); |
| getElementById("view_sponge_invocation_wrapper").getStyle().setProperty("display", "none"); |
| JSONObject params = new JSONObject(); |
| params.put("afe_job_id", new JSONNumber(jobId)); |
| AfeUtils.callGetSpongeUrl(params, new SimpleCallback() { |
| public void doCallback(Object spongeUrl) { |
| if (spongeUrl != null){ |
| getElementById("view_sponge_invocation_wrapper").getStyle().setProperty("display", ""); |
| getElementById("view_sponge_invocation").setAttribute("href", spongeUrl.toString()); |
| getElementById("view_sponge_invocation").setInnerHTML(spongeUrl.toString()); |
| } |
| } |
| }); |
| } |
| } |