package maps.hudson.plugin.xfpanel; import hudson.Extension; import hudson.Functions; import hudson.Util; import hudson.model.*; import hudson.model.Descriptor.FormException; import hudson.tasks.test.AbstractTestResultAction; import hudson.util.FormValidation; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.servlet.ServletException; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * Represents an eXtreme Feedback Panel View. * * Thanks to Mark Howard and his work on the Radiator View Plugin from which this was based. * * @author jrenaut */ public class XFPanelView extends ListView { private XFColors colors; private Integer numColumns = 1; private Integer refresh = 3; private Boolean fullHD = false; private Boolean showDescription = false; private Boolean showZeroTestCounts = true; private Boolean sortDescending = false; private transient List<XFPanelEntry> entries; private Map<hudson.model.Queue.Item, Integer> placeInQueue = new HashMap<hudson.model.Queue.Item, Integer>(); /** * C'tor<meta /> * @param name the name of the view * @param numColumns the number of columns to use on the layout (work in progress) */ @DataBoundConstructor public XFPanelView(String name, Integer numColumns) { super(name); this.numColumns = numColumns != null ? numColumns : 1; } /** * @return the colors to use */ public XFColors getColors() { if (this.colors == null) { this.colors = XFColors.DEFAULT; } return this.colors; } public Boolean getFullHD() { return this.fullHD; } public Boolean getShowDescription() { if (this.showDescription == null) { this.showDescription = false; } return this.showDescription; } public Boolean getSortDescending() { if (this.sortDescending == null) { this.sortDescending = false; } return this.sortDescending; } public Boolean getShowZeroTestCounts() { if (this.showZeroTestCounts == null) { this.showZeroTestCounts = true; } return this.showZeroTestCounts; } /** * @param jobs the selected jobs * @return the jobs list wrapped into {@link XFPanelEntry} instances */ public Collection<XFPanelEntry> sort(Collection<Job<?, ?>> jobs) { placeInQueue = new HashMap<hudson.model.Queue.Item, Integer>(); int j = 1; for(hudson.model.Queue.Item i : Hudson.getInstance().getQueue().getItems()) { placeInQueue.put(i, j++); } if (jobs != null) { List<XFPanelEntry> ents = new ArrayList<XFPanelEntry>(); for (Job<?, ?> job : jobs) { ents.add(new XFPanelEntry(job)); } if (this.getSortDescending()) { Collections.reverse(ents); } this.entries = ents; return this.entries; } return Collections.emptyList(); } /** * @return the refresh time in seconds */ public Integer getRefresh() { return this.refresh; } /** * @return the numColumns */ public Integer getNumColumns() { return this.numColumns; } /** * Gets from the request the configuration parameters * * @param req {@link StaplerRequest} * * @throws ServletException if any * @throws FormException if any */ @Override protected void submit(StaplerRequest req) throws ServletException, FormException { super.submit(req); try { this.numColumns = Integer.parseInt(req.getParameter("numColumns")); } catch (NumberFormatException e) { throw new FormException(XFPanelViewDescriptor.MSG, "numColumns"); } try { this.refresh = Integer.parseInt(req.getParameter("refresh")); } catch (NumberFormatException e) { throw new FormException(XFPanelViewDescriptor.REFRESH_MSG, "refresh"); } this.fullHD = Boolean.parseBoolean(req.getParameter("fullHD")); this.showDescription = Boolean.parseBoolean(req.getParameter("showDescription")); this.sortDescending = Boolean.parseBoolean(req.getParameter("sortDescending")); this.showZeroTestCounts = Boolean.parseBoolean(req.getParameter("showZeroTestCounts")); } /** * Represents a job to be shown on the panel * * Intermediates access to data available for the given Job * * @author jrenaut */ public final class XFPanelEntry { private Job<?, ?> job; private String backgroundColor; private String color; private Boolean broken; private Boolean building = false; private Boolean queued = false; private Integer queueNumber; /** * C'tor * @param job the job to be represented */ public XFPanelEntry(Job<?, ?> job) { this.job = job; this.findStatus(); } /** * @return the job */ public Job<?, ?> getJob() { return this.job; } /** * @return the job's name */ public String getName() { String label = job.getName().toUpperCase(); if (getShowDescription() == true && !job.getDescription().isEmpty()) { label += ": " + job.getDescription(); } return label; } /** * @return if this job is queued for build */ public Boolean getQueued() { return this.job.isInQueue(); } /** * @return the job's queue number, if any */ public Integer getQueueNumber() { return placeInQueue.get(this.job.getQueueItem()); } /** * @return background color for this job */ public String getBackgroundColor() { return this.backgroundColor; } /** * @return foreground color for this job */ public String getColor() { return this.color; } /** * @return true se o último build está quebrado */ public Boolean getBroken() { return this.broken; } /** * @return true if this job is currently being built */ public Boolean getBuilding() { return this.building; } /** * @return the URL for the last build */ public String getUrl() { return this.job.getUrl() + "lastBuild"; } /** * @return a list will all the currently building runs for this job. */ public List<Run<? , ?>> getBuildsInProgress() { List<Run<?, ?>> runs = new ArrayList<Run<?, ?>>(); Run<?, ? > run = this.job.getLastBuild(); if (run.isBuilding()) { runs.add(run); } Run<?, ?> prev = run.getPreviousBuildInProgress(); while (prev != null) { runs.add(prev); prev = prev.getPreviousBuildInProgress(); } return runs; } /** * @return total tests executed */ public int getTestCount() { Run<?, ?> run = this.job.getLastSuccessfulBuild(); if (run != null) { AbstractTestResultAction<?> tests = run.getAction(AbstractTestResultAction.class); return tests != null ? tests.getTotalCount() : 0; } return 0; } /** * @return total failed tests */ public int getFailCount() { Run<?, ?> run = this.job.getLastSuccessfulBuild(); if (run != null) { AbstractTestResultAction<?> tests = run.getAction(AbstractTestResultAction.class); return tests != null ? tests.getFailCount() : 0; } return 0; } /** * @return total successful tests */ public int getSuccessCount() { return this.getTestCount() - this.getFailCount(); } /** * @return difference between this job's last build successful tests and the previous' */ public String getDiff() { Run<?, ?> run = this.job.getLastSuccessfulBuild(); if (run != null) { Run<?, ?> previous = this.getLastSuccessfulFrom(run); if (previous != null) { AbstractTestResultAction<?> tests = run.getAction(AbstractTestResultAction.class); AbstractTestResultAction<?> prevTests = previous.getAction(AbstractTestResultAction.class); if (tests != null && prevTests != null) { int currentSuccess = tests.getTotalCount() - tests.getFailCount(); int prevSuccess = prevTests.getTotalCount() - prevTests.getFailCount(); return Functions.getDiffString(currentSuccess-prevSuccess); } } } return ""; } /** * @param run a run * @return the last successful run prior to the given run */ private Run<?, ?> getLastSuccessfulFrom(Run<?, ?> run) { Run<?, ?> r = run.getPreviousBuild(); while (r != null && (r.isBuilding() || r.getResult() == null || r.getResult() .isWorseThan(Result.UNSTABLE))) { r = r.getPreviousBuild(); } return r; } /** * Elects a culprit/responsible for a broken build by choosing the last commiter of a given build * * @return the culprit/responsible */ public String getCulprit() { Run<?, ?> run = this.job.getLastBuild(); String culprit = " - "; if (run instanceof AbstractBuild<?, ?>) { AbstractBuild<?, ?> build = (AbstractBuild<?, ?>) run; Iterator<User> it = build.getCulprits().iterator(); while (it.hasNext()) { culprit = it.next().getFullName().toUpperCase(); } } return culprit; } /** * @return color to be used to show the test diff */ public String getDiffColor() { String diff = this.getDiff().trim(); if (diff.length() > 0) { if (diff.startsWith("-")) { return "#FF0000"; } else { return "#00FF00"; } } return "#FFFFFF"; } /** * @return the percentage of successful tests versus the total */ public String getSuccessPercentage() { if (this.getTestCount() > 0) { Double perc = (this.getSuccessCount() / (this.getTestCount() * 1D)); return NumberFormat.getPercentInstance().format(perc); } return ""; } /** * Determines some information of the current job like which colors use, wether it's building or not or broken. */ private void findStatus() { switch (this.job.getIconColor()) { case BLUE_ANIME: this.building = true; case BLUE: this.backgroundColor = getColors().getOkBG(); this.color = colors.getOkFG(); this.broken = false; break; case YELLOW_ANIME: this.building = true; case YELLOW: this.backgroundColor = getColors().getFailedBG(); this.color = colors.getFailedFG(); this.broken = false; break; case RED_ANIME: this.building = true; case RED: this.backgroundColor = getColors().getBrokenBG(); this.color = colors.getBrokenFG(); this.broken = true; break; case GREY_ANIME: case DISABLED_ANIME: this.building = true; default: this.backgroundColor = getColors().getOtherBG(); this.color = colors.getOtherFG(); this.broken = true; } } } /** * Notify Hudson we're implementing a new View * @author jrenaut */ @Extension public static final class XFPanelViewDescriptor extends ViewDescriptor { public static final String REFRESH_MSG = "Refresh time must be a positive integer."; public static final String MSG = "Number of columns currently supported is 1 or 2."; /** * {@inheritDoc} */ @Override public String getDisplayName() { return "eXtreme Feedback Panel"; } /** * Performs validation on request parameters * @param req request * @param resp response * @return a form validation */ public FormValidation doCheckNumColumns(StaplerRequest req, StaplerResponse resp) { String num = Util.fixEmptyAndTrim(req.getParameter("numColumns")); if (num != null) { try { int i = Integer.parseInt(num); if (i < 1 || i > 2) { return FormValidation.error(MSG); } } catch (NumberFormatException e) { return FormValidation.error(MSG); } } else { return FormValidation.error(MSG); } return FormValidation.ok(); } public FormValidation doCheckRefresh(StaplerRequest req) { return FormValidation.validatePositiveInteger(req.getParameter("refresh")); } } /** * Represents colors to be used on the view * @author jrenaut */ public static final class XFColors { private String okBG; private String okFG; private String failedBG; private String failedFG; private String brokenBG; private String brokenFG; private String otherBG; private String otherFG; /** * C'tor * @param okBG ok builds background color * @param okFG ok builds foreground color * @param failedBG failed build background color * @param failedFG failed build foreground color * @param brokenBG broken build background color * @param brokenFG broken build foreground color * @param otherBG other build background color * @param otherFG other build foreground color */ public XFColors(String okBG, String okFG, String failedBG, String failedFG, String brokenBG, String brokenFG, String otherBG, String otherFG) { super(); this.okBG = okBG; this.okFG = okFG; this.failedBG = failedBG; this.failedFG = failedFG; this.brokenBG = brokenBG; this.brokenFG = brokenFG; this.otherBG = otherBG; this.otherFG = otherFG; } /** * @return the okBG */ public String getOkBG() { return okBG; } /** * @return the okFG */ public String getOkFG() { return okFG; } /** * @return the failedBG */ public String getFailedBG() { return failedBG; } /** * @return the failedFG */ public String getFailedFG() { return failedFG; } /** * @return the brokenBG */ public String getBrokenBG() { return brokenBG; } /** * @return the brokenFG */ public String getBrokenFG() { return brokenFG; } /** * @return the otherBG */ public String getOtherBG() { return otherBG; } /** * @return the otherFG */ public String getOtherFG() { return otherFG; } public static final XFColors DEFAULT = new XFColors("#7E7EFF", "#FFFFFF", "#FFC130", "#FFFFFF", "#FF0000", "#FFFFFF", "#CCCCCC", "#FFFFFF"); } }