Java tutorial
/******************************************************************************* * Copyright (c) 2013 Cloud Bees, Inc. * All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloud Bees, Inc. - initial API and implementation *******************************************************************************/ package com.cloudbees.eclipse.dev.ui.views.jobs; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.progress.PendingUpdateAdapter; import com.cloudbees.eclipse.core.CBRemoteChangeAdapter; import com.cloudbees.eclipse.core.CBRemoteChangeListener; import com.cloudbees.eclipse.core.CloudBeesException; import com.cloudbees.eclipse.core.JenkinsService; import com.cloudbees.eclipse.core.jenkins.api.JenkinsBuild; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.Job; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.JobViewGeneric; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse.View; import com.cloudbees.eclipse.core.util.Utils; import com.cloudbees.eclipse.dev.ui.CBDEVImages; import com.cloudbees.eclipse.dev.ui.CloudBeesDevUiPlugin; import com.cloudbees.eclipse.dev.ui.actions.DeleteJobAction; import com.cloudbees.eclipse.dev.ui.actions.InvokeBuildAction; import com.cloudbees.eclipse.dev.ui.actions.OpenBuildAction; import com.cloudbees.eclipse.dev.ui.actions.OpenLogAction; import com.cloudbees.eclipse.dev.ui.actions.ReloadBuildHistoryAction; import com.cloudbees.eclipse.dev.ui.actions.ReloadJobsAction; import com.cloudbees.eclipse.ui.CloudBeesUIPlugin; import com.cloudbees.eclipse.ui.PreferenceConstants; /** * View showing jobs for both Jenkins on-premise installations and DEV@cloud jenkins instances * * @author ahtik */ public class JobsView extends ViewPart implements IPropertyChangeListener { public static final String ID = "com.cloudbees.eclipse.dev.ui.views.jobs.JobsView"; private TreeViewer treeViewer; protected ReloadJobsAction actionReloadJobs; private InvokeBuildAction actionInvokeBuild; private Action actionOpenJobInBrowser; private OpenBuildAction actionOpenLastBuildDetails; private ReloadBuildHistoryAction actionOpenBuildHistory; private OpenLogAction actionOpenLog; private Action actionDeleteJob; private final Map<String, Image> stateIcons = new HashMap<String, Image>(); private CBRemoteChangeListener jenkinsChangeListener; private JobsContentProvider contentProvider; protected Runnable regularRefresher; private String viewUrl; protected JobHolder selectedJobHolder; public JobsView() { super(); } public JobHolder getSelectedJob() { return this.selectedJobHolder; } public void setSelectedJob(final JobHolder job) { this.selectedJobHolder = job; boolean enable = this.selectedJobHolder != null; this.actionInvokeBuild.setJob(this.selectedJobHolder); this.actionOpenLastBuildDetails.setBuild( getJob(this.selectedJobHolder) instanceof Job ? ((Job) (getJob(this.selectedJobHolder))).lastBuild : null); this.actionOpenLog.setBuild( getJob(this.selectedJobHolder) instanceof Job ? ((Job) getJob(this.selectedJobHolder)).lastBuild : null); this.actionDeleteJob.setEnabled(getJob(this.selectedJobHolder) instanceof Job && ((Job) getJob(this.selectedJobHolder)).color != null); this.actionOpenJobInBrowser.setEnabled(enable); this.actionOpenBuildHistory.setViewUrl(getJob(this.selectedJobHolder) instanceof Job && ((Job) getJob(this.selectedJobHolder)).color != null ? ((Job) getJob(this.selectedJobHolder)).url : null); //FIXME color!=null is currently the only known way to know if this job is not a folder. this.actionOpenBuildHistory.setEnabled(getJob(this.selectedJobHolder) instanceof Job && ((Job) getJob(this.selectedJobHolder)).color != null); } /** * Helper method to return the JobViewGeneric from the holder which is expected to be instanceof JobHolder. Param is * Object to provider easier usage. * * @param holder * @return */ private JobViewGeneric getJob(Object holder) { if (holder == null) return null; if (!(holder instanceof JobHolder)) { return null;//throw new RuntimeException("Unexpected holder type! Expected JobHelper, got "+holder); } return ((JobHolder) holder).job; } public ReloadJobsAction getReloadJobsAction() { return this.actionReloadJobs; } protected void setInput(final JenkinsJobsResponse newView) { if (!isCurrentView(newView.viewUrl)) { return; // another view } if (newView == null || (newView.jobs == null && newView.views == null)) { String post = ""; if (newView.name != null && newView.name.length() > 0) { post = " for " + newView.name; } setContentDescription(" No jobs available" + post); this.contentProvider.inputChanged(this.treeViewer, null, new ArrayList<JobHolder>()); } else { JenkinsService ss = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(newView.viewUrl); String label = ss.getLabel(); String viewInfo = ""; String post = ""; if (newView.name != null && newView.name.length() > 0 && label != null && !newView.name.equals(label)) { viewInfo = newView.name + " ["; post = "#" + newView.name; } setContentDescription( " " + viewInfo + label + (viewInfo.length() > 0 ? "]" : "") + " (" + new Date() + ")"); setPartName("Build Jobs [" + label + post + "]"); List<JobHolder> reslist = new ArrayList<JobHolder>(); // Also add views if it's not the main url if (!newView.viewUrl.equals(ss.getUrl() + "/") && newView.views != null) { for (View view : newView.views) { if (view.url != null && (newView.primaryView == null || !view.url.equals(newView.primaryView.url))) { reslist.add(new JobHolder(view, null)); } } } if (newView.jobs != null) { for (Job job : newView.jobs) { reslist.add(new JobHolder(job, null)); } } this.contentProvider.inputChanged(this.treeViewer, null, reslist); } if (newView != null) { this.viewUrl = newView.viewUrl; } this.actionReloadJobs.viewUrl = this.viewUrl; this.actionReloadJobs.treeViewer = this.treeViewer; // Preserve the expended state and refresh //TreePath[] paths = treeViewer.getExpandedTreePaths(); this.treeViewer.refresh(); //treeViewer.setExpandedTreePaths(paths); boolean reloadable = newView != null; this.actionReloadJobs.setEnabled(reloadable); if (reloadable) { startRefresher(); } else { stopRefresher(); } } private boolean isCurrentView(String viewUrl) { if (viewUrl != null) { IViewSite site = getViewSite(); String secId = site.getSecondaryId(); String servUrl = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl).getUrl(); if (secId != null && servUrl != null && !secId.equals(Long.toString(servUrl.hashCode()))) { return false; // another view } } return true; } protected synchronized void stopRefresher() { this.regularRefresher = null; } protected synchronized void startRefresher() { if (this.regularRefresher != null) { return; // already running } if (this.viewUrl == null) { return; // nothing to refresh anyway } boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED); int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL); if (!enabled || secs <= 0) { return; // disabled } this.regularRefresher = new Runnable() { @Override public void run() { if (JobsView.this.regularRefresher == null) { return; } try { CloudBeesDevUiPlugin.getDefault().showJobs(JobsView.this.actionReloadJobs.viewUrl, false); } catch (CloudBeesException e) { CloudBeesUIPlugin.getDefault().getLogger().error(e); } finally { if (JobsView.this.regularRefresher != null) { // not already stopped boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED); int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL); if (enabled && secs > 0) { PlatformUI.getWorkbench().getDisplay().timerExec(secs * 1000, JobsView.this.regularRefresher); } else { stopRefresher(); } } } } }; PlatformUI.getWorkbench().getDisplay().timerExec(secs * 1000, this.regularRefresher); } class NameSorter extends ViewerSorter { @Override public int compare(final Viewer viewer, Object e1, Object e2) { if (e1 instanceof JobHolder && e2 instanceof JobHolder) { e1 = ((JobHolder) e1).job; e2 = ((JobHolder) e2).job; } if (e1 instanceof JenkinsJobsResponse.JobViewGeneric && e2 instanceof JenkinsJobsResponse.JobViewGeneric) { JenkinsJobsResponse.JobViewGeneric j1 = (JenkinsJobsResponse.JobViewGeneric) e1; JenkinsJobsResponse.JobViewGeneric j2 = (JenkinsJobsResponse.JobViewGeneric) e2; String displayName1 = j1.getName(); String displayName2 = j2.getName(); if (displayName1 != null && displayName2 != null) { return displayName1.toLowerCase().compareTo(displayName2.toLowerCase()); } } return super.compare(viewer, e1, e2); } } @Override public void createPartControl(final Composite parent) { initImages(); this.treeViewer = new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.VIRTUAL); treeViewer.getTree().setHeaderVisible(true); initColumns(); this.contentProvider = new JobsContentProvider(getViewSite()); this.treeViewer.setContentProvider(this.contentProvider); this.treeViewer.setSorter(new JobSorter(JobSorter.JOB)); this.treeViewer.setInput(getViewSite()); this.treeViewer.addOpenListener(new IOpenListener() { @Override public void open(final OpenEvent event) { ISelection sel = event.getSelection(); if (sel instanceof IStructuredSelection) { Object el = ((IStructuredSelection) sel).getFirstElement(); if (el instanceof JobHolder && ((JobHolder) el).job instanceof Job) { Job job = (Job) ((JobHolder) el).job; //assuming it's a folder.. if (job.isFolderOrView()) { JobsView.this.toggle(el); return; // do nothing } else { CloudBeesDevUiPlugin.getDefault().showBuildForJob(job); } } if (el instanceof JobHolder && ((JobHolder) el).job instanceof View) { JobsView.this.toggle(el); return; // do nothing } } } }); makeActions(); contributeToActionBars(); MenuManager popupMenu = new MenuManager(); popupMenu.add(this.actionOpenLastBuildDetails); popupMenu.add(this.actionOpenLog); popupMenu.add(this.actionOpenBuildHistory); popupMenu.add(new Separator()); popupMenu.add(this.actionOpenJobInBrowser); popupMenu.add(this.actionInvokeBuild); popupMenu.add(new Separator()); popupMenu.add(this.actionDeleteJob); popupMenu.add(new Separator()); popupMenu.add(this.actionReloadJobs); Menu menu = popupMenu.createContextMenu(treeViewer.getTree()); treeViewer.getTree().setMenu(menu); treeViewer.getTree().setSortDirection(SWT.DOWN); treeViewer.addTreeListener(new ITreeViewerListener() { @Override public void treeExpanded(TreeExpansionEvent event) { } @Override public void treeCollapsed(TreeExpansionEvent event) { // if something was collapsed make sure it won't be expanded by deferred loaders contentProvider.removeDeferredExpanders(event.getElement()); } }); this.treeViewer.addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(final SelectionChangedEvent event) { StructuredSelection sel = (StructuredSelection) event.getSelection(); if (sel.getFirstElement() instanceof JobHolder) { setSelectedJob((JobHolder) sel.getFirstElement()); } } }); this.jenkinsChangeListener = new CBRemoteChangeAdapter() { public void activeAccountChanged(String email, String newAccountName) { // if cloud-hosted view and account changed, close this view boolean closeView = false; if (viewUrl == null || viewUrl.length() == 0) { closeView = true; } if (!closeView) { JenkinsService ss = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl); if (ss == null || ss.isCloud()) { closeView = true; } } if (closeView) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getActivePage(); page.hideView(JobsView.this); } }); } } public void activeJobViewChanged(final JenkinsJobsResponse newView) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { JobsView.this.setInput(newView); } }); } public void jenkinsStatusUpdate(String viewUrl, boolean online) { if (!isCurrentView(viewUrl)) { return; // nothing to do, same view } // For now server status change is only relevant when it's offline if (!online) { final JenkinsService s = CloudBeesUIPlugin.getDefault().getJenkinsServiceForUrl(viewUrl); PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { setPartName("Build Jobs [" + s.getLabel() + "] (offline)"); setContentDescription(" " + s.getLabel() + " (" + s.getUrl() + ") not available (" + new Date() + ")"); } }); } } }; CloudBeesUIPlugin.getDefault().addCBRemoteChangeListener(this.jenkinsChangeListener); } protected void toggle(Object el) { boolean exp = treeViewer.getExpandedState(el); if (exp) { treeViewer.collapseToLevel(el, 1); } else { treeViewer.expandToLevel(el, 1); } } protected String formatBuildInfo(final JenkinsBuild build) { String unit = ""; if (build.duration != null) { //TODO Implement proper human-readable duration conversion, consider using the same conversion rules that Jenkins uses //CloudBeesUIPlugin.getDefault().getLogger().info("DURATION: " + build.timestamp); unit = Utils.humanReadableTime((System.currentTimeMillis() - build.timestamp.longValue())); } String timeComp = build.duration != null ? /*", " + */unit + " ago" : ""; //String buildComp = build.number != null ? "#" + build.number : "n/a"; String buildComp = build.number != null ? " #" + build.number : "n/a"; return timeComp + buildComp; } private void initImages() { String[] icons = { "blue", "red", "yellow", "grey" }; for (int i = 0; i < icons.length; i++) { //TODO Refactor to use CBImages! Image img = ImageDescriptor.createFromURL(CloudBeesDevUiPlugin.getDefault().getBundle() .getResource("/icons/jenkins-icons/16x16/" + icons[i] + ".gif")).createImage(); this.stateIcons.put(icons[i], img); } this.stateIcons.put("disabled", ImageDescriptor.createFromURL( CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/grey.gif")) .createImage()); this.stateIcons.put("folder", ImageDescriptor.createFromURL( CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/folder.gif")) .createImage()); this.stateIcons.put("aborted", ImageDescriptor.createFromURL( CloudBeesDevUiPlugin.getDefault().getBundle().getResource("/icons/jenkins-icons/16x16/stop.gif")) .createImage()); } /* private TreeViewerColumn createColumn(final String colName, final int width, ColumnLabelProvider labelProvider) { return createColumn(colName, width, -1, labelProvider); }*/ private TreeViewerColumn createColumn(final String colName, final int width, final int sortCol, ColumnLabelProvider labelProvider) { final TreeViewerColumn col = new TreeViewerColumn(treeViewer, SWT.NONE); col.setLabelProvider(labelProvider); if (width > 0) { col.getColumn().setWidth(width); } col.getColumn().setText(colName); col.getColumn().setMoveable(true); if (sortCol >= 0) { col.getColumn().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { int newOrder = SWT.DOWN; if (treeViewer.getTree().getSortColumn().equals(col) && treeViewer.getTree().getSortDirection() == SWT.DOWN) { newOrder = SWT.UP; } treeViewer.getTree().setSortColumn(col.getColumn()); treeViewer.getTree().setSortDirection(newOrder); JobSorter newSorter = new JobSorter(sortCol); newSorter.setDirection(newOrder); JobsView.this.treeViewer.setSorter(newSorter); } }); } return col; } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown(bars.getMenuManager()); fillLocalToolBar(bars.getToolBarManager()); } private void fillLocalToolBar(final IToolBarManager manager) { //manager.add(this.actionOpenLastBuildDetails); manager.add(this.actionOpenJobInBrowser); manager.add(this.actionInvokeBuild); manager.add(new Separator()); manager.add(this.actionReloadJobs); } private void fillLocalPullDown(final IMenuManager manager) { manager.add(this.actionOpenLastBuildDetails); manager.add(this.actionOpenLog); } private void makeActions() { CloudBeesUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this); this.actionReloadJobs = new ReloadJobsAction(); this.actionReloadJobs.setEnabled(false); this.actionOpenLastBuildDetails = new OpenBuildAction(true); this.actionOpenBuildHistory = new ReloadBuildHistoryAction(false); this.actionOpenLog = new OpenLogAction(); this.actionDeleteJob = new DeleteJobAction(this); this.actionOpenJobInBrowser = new Action("Open with Browser...", Action.AS_PUSH_BUTTON | SWT.NO_FOCUS) { //$NON-NLS-1$ @Override public void run() { if (JobsView.this.selectedJobHolder != null && getJob(JobsView.this.selectedJobHolder) instanceof JobViewGeneric) { JenkinsJobsResponse.JobViewGeneric job = getJob(JobsView.this.selectedJobHolder); CloudBeesUIPlugin.getDefault().openWithBrowser(job.getUrl()); } } }; this.actionOpenJobInBrowser.setToolTipText("Open with Browser"); //TODO i18n this.actionOpenJobInBrowser .setImageDescriptor(CloudBeesDevUiPlugin.getImageDescription(CBDEVImages.IMG_BROWSER)); this.actionOpenJobInBrowser.setEnabled(false); this.actionInvokeBuild = new InvokeBuildAction(); CloudBeesUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this); } @Override public void setFocus() { this.treeViewer.getControl().setFocus(); } @Override public void propertyChange(final PropertyChangeEvent event) { if (PreferenceConstants.P_JENKINS_INSTANCES.equals(event.getProperty()) || PreferenceConstants.P_EMAIL.equals(event.getProperty()) || PreferenceConstants.P_PASSWORD.equals(event.getProperty())) { try { if (viewUrl != null) { CloudBeesDevUiPlugin.getDefault().showJobs(this.viewUrl, false); } } catch (CloudBeesException e) { //TODO i18n CloudBeesUIPlugin.showError("Failed to reload Jenkins jobs!", e); } } if (PreferenceConstants.P_JENKINS_REFRESH_INTERVAL.equals(event.getProperty()) || PreferenceConstants.P_JENKINS_REFRESH_ENABLED.equals(event.getProperty())) { boolean enabled = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getBoolean(PreferenceConstants.P_JENKINS_REFRESH_ENABLED); int secs = CloudBeesUIPlugin.getDefault().getPreferenceStore() .getInt(PreferenceConstants.P_JENKINS_REFRESH_INTERVAL); if (enabled && secs > 0) { startRefresher(); // start it if it was disabled by 0 value, do nothing if it was already running } else { stopRefresher(); } } } @Override public void dispose() { CloudBeesUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this); CloudBeesUIPlugin.getDefault().removeCBRemoteChangeListener(this.jenkinsChangeListener); this.jenkinsChangeListener = null; stopRefresher(); disposeImages(); super.dispose(); } private void disposeImages() { Iterator<Image> it = this.stateIcons.values().iterator(); while (it.hasNext()) { Image img = it.next(); img.dispose(); } this.stateIcons.clear(); } private void initColumns() { TreeViewerColumn namecol = createColumn("Job", 250, JobSorter.JOB, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { Object el = cell.getViewerRow().getElement(); if (el instanceof PendingUpdateAdapter) { PendingUpdateAdapter uel = (PendingUpdateAdapter) el; cell.setText(uel.getLabel(null)); cell.setImage(null); return; } JobViewGeneric vg = ((JobHolder) cell.getViewerRow().getElement()).job; if (vg instanceof JenkinsJobsResponse.Job) { JenkinsJobsResponse.Job job = (Job) vg; String val = job.getDisplayName(); if (job.inQueue != null && job.inQueue) { val = val + " (in queue)"; } else if (job.color != null && job.color.indexOf('_') > 0) { val = val + " (running)"; } cell.setText(val); String key = job.color; if (job.color != null && job.color.contains("_")) { key = job.color.substring(0, job.color.indexOf("_")); } // Assuming for now that these are all folders as thers's no API to tell the difference if (job.color == null) { key = "folder"; } Image img = JobsView.this.stateIcons.get(key); if (img != null) { cell.setImage(img); } else if (job.color != null) { cell.setText(val + "[" + job.color + "]"); } } if (vg instanceof View) { cell.setImage(CloudBeesDevUiPlugin.getImage(CBDEVImages.IMG_VIEWR2)); cell.setText(vg.getName()); } } }); createColumn("Build stability", 250, JobSorter.BUILD_STABILITY, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { Object element = getJob(cell.getViewerRow().getElement()); if ((!(element instanceof JobViewGeneric)) || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) { cell.setText(""); cell.setImage(null); return; } JenkinsJobsResponse.Job job = (Job) element; cell.setText(""); cell.setImage(null); try { if (job.healthReport != null) { for (int h = 0; h < job.healthReport.length; h++) { String icon = job.healthReport[h].iconUrl; String desc = job.healthReport[h].description; String matchStr = "Build stability: "; if (desc != null && desc.startsWith(matchStr)) { cell.setText(" " + desc.substring(matchStr.length())); cell.setImage(CloudBeesDevUiPlugin .getImage(CBDEVImages.IMG_HEALTH_PREFIX + CBDEVImages.IMG_16 + icon)); } } } } catch (Throwable t) { t.printStackTrace(); } } }); createColumn("Last build", 150, JobSorter.LAST_BUILD, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { Object element = getJob(cell.getViewerRow().getElement()); if ((!(element instanceof JobViewGeneric)) || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) { cell.setText(""); cell.setImage(null); return; } JenkinsJobsResponse.Job job = (Job) element; try { cell.setText(JobsView.this.formatBuildInfo(job.lastBuild)); } catch (Throwable t) { cell.setText(""); } } }); createColumn("Last success", 150, JobSorter.LAST_SUCCESS, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { Object element = getJob(cell.getViewerRow().getElement()); if ((!(element instanceof JobViewGeneric)) || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) { cell.setText(""); cell.setImage(null); return; } JenkinsJobsResponse.Job job = (Job) element; try { cell.setText(JobsView.this.formatBuildInfo(job.lastSuccessfulBuild)); } catch (Throwable t) { cell.setText(""); } } }); createColumn("Last failure", 150, JobSorter.LAST_FAILURE, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { Object element = getJob(cell.getViewerRow().getElement()); if ((!(element instanceof JobViewGeneric)) || (element instanceof JobViewGeneric) && ((JobViewGeneric) element).isFolderOrView()) { cell.setText(""); cell.setImage(null); return; } JenkinsJobsResponse.Job job = (Job) element; try { cell.setText(JobsView.this.formatBuildInfo(job.lastFailedBuild)); } catch (Throwable t) { cell.setText(""); } } }); } }