package com.axis.system.jenkins.plugins.downstream.yabv; import static com.axis.system.jenkins.plugins.downstream.tree.TreeLaminator.layoutTree; import com.axis.system.jenkins.plugins.downstream.cache.BuildCache; import com.axis.system.jenkins.plugins.downstream.tree.Matrix; import com.axis.system.jenkins.plugins.downstream.tree.TreeLaminator.ChildrenFunction; import hudson.Extension; import hudson.model.Action; import hudson.model.Api; import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.Job; import hudson.model.Queue; import hudson.model.Run; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.annotation.Nonnull; import javax.servlet.ServletException; import jenkins.model.Jenkins; import jenkins.model.TransientActionFactory; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * Produces Transient Actions for visualizing the flow of downstream builds. * * @author Gustaf Lundh (C) Axis 2018 */ @SuppressWarnings("unused") @ExportedBean public class BuildFlowAction implements Action { private final Run target; private final BuildFlowOptions buildFlowOptions; private BuildFlowAction(Run run) { this.target = run; this.buildFlowOptions = new BuildFlowOptions(); } private static final ChildrenFunction getChildrenFunc() { final Queue.Item[] items = Queue.getInstance().getItems(); return build -> { List<Object> result = new ArrayList<>(); if (build instanceof Run) { result.addAll(BuildCache.getCache().getDownstreamBuilds((Run) build)); result.addAll(BuildCache.getDownstreamQueueItems(items, (Run) build)); } return result; }; }; private static Run getRootUpstreamBuild(@Nonnull Run build) { Run parentBuild; while ((parentBuild = getUpstreamBuild(build)) != null) { build = parentBuild; } return build; } private static Run getUpstreamBuild(@Nonnull Run build) { CauseAction causeAction = build.getAction(CauseAction.class); if (causeAction == null) { return null; } for (Cause cause : causeAction.getCauses()) { if (cause instanceof Cause.UpstreamCause) { Cause.UpstreamCause upstreamCause = (Cause.UpstreamCause) cause; Job upstreamJob = Jenkins.getInstance().getItemByFullName(upstreamCause.getUpstreamProject(), Job.class); // We want to ignore rebuilds, rebuilds have the same parent as // original build, see BuildCache#updateCache(). if (upstreamJob == null || build.getParent() == upstreamJob) { continue; } return upstreamJob.getBuildByNumber(upstreamCause.getUpstreamBuild()); } } return null; } public BuildFlowOptions getBuildFlowOptions() { return buildFlowOptions; } @Exported(visibility = 1) public boolean isAnyBuildOngoing() { return target != null && isChildrenStillBuilding(getRootUpstreamBuild(target), getChildrenFunc()); } private static boolean isChildrenStillBuilding(Object current, ChildrenFunction children) { Iterator childIter = children.children(current).iterator(); while (childIter.hasNext()) { Object child = childIter.next(); if (child instanceof Queue.Item) { return true; } if (child instanceof Run && ((Run) child).isBuilding()) { return true; } return isChildrenStillBuilding(child, children); } return false; } @Exported(visibility = 1) public boolean isCacheRefreshing() { return BuildCache.getCache().isCacheRefreshing(); } public boolean shouldDisplayBuildFlow() { return target != null && (hasUpstreamOrDownstreamBuilds(target) || hasUpstreamOrDownstreamBuilds(target.getParent().getLastCompletedBuild()) || hasUpstreamOrDownstreamBuilds(target.getParent().getLastBuild())); } public static boolean hasUpstreamOrDownstreamBuilds(Run target) { if (target == null) { return false; } return BuildCache.getCache().getDownstreamBuilds(target).size() > 0 || BuildCache.getDownstreamQueueItems(target).size() > 0 || getUpstreamBuild(target) != null; } public Run getTarget() { return target; } public Matrix buildMatrix() { if (target == null) { return new Matrix(); } return layoutTree((Object) getRootUpstreamBuild(target), getChildrenFunc()); } @Override public String getDisplayName() { return null; } @Override public String getUrlName() { return "yabv"; } @Override public String getIconFileName() { return null; } public void doBuildFlow(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { buildFlowOptions.setShowDurationInfo( Boolean.parseBoolean(req.getParameter("showDurationInfo"))); buildFlowOptions.setShowBuildHistory( Boolean.parseBoolean(req.getParameter("showBuildHistory"))); rsp.setContentType("text/html;charset=UTF-8"); req.getView(this, "buildFlow.groovy").forward(req, rsp); } /** Remote API access. */ public Api getApi() { return new Api(this); } @Extension public static class BuildActionFactory extends TransientActionFactory<Run> { @Override public Class<Run> type() { return Run.class; } @Override public Collection<? extends Action> createFor(Run run) { return Collections.singleton(new BuildFlowAction(run)); } } @Extension(ordinal = 1000) public static class ProjectActionFactory extends TransientActionFactory<Job> { @Override public Class<Job> type() { return Job.class; } @Override public Collection<? extends Action> createFor(@Nonnull Job target) { Run build = target.getLastBuild(); if (build == null) { return Collections.emptyList(); } else { return Collections.singleton(new BuildFlowAction(build)); } } } }