/* This file is part of Delivery Pipeline Plugin. Delivery Pipeline Plugin is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Delivery Pipeline Plugin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Delivery Pipeline Plugin. If not, see <http://www.gnu.org/licenses/>. */ package se.diabol.jenkins.pipeline.domain; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import com.google.common.collect.ImmutableList; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.ItemGroup; import hudson.model.Result; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import se.diabol.jenkins.pipeline.domain.task.Task; import se.diabol.jenkins.pipeline.sort.BuildStartTimeComparator; import se.diabol.jenkins.pipeline.util.PipelineUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @ExportedBean(defaultVisibility = AbstractItem.VISIBILITY) public class Pipeline extends AbstractItem { private final AbstractProject firstProject; private final AbstractProject lastProject; private final List<Stage> stages; private String version; private List<TriggerCause> triggeredBy; private Set<UserInfo> contributors; private boolean aggregated; private String timestamp; private List<Change> changes; private int commits; private long totalBuildTime; private Map<String, Task> allTasks = null; public Pipeline(String name, AbstractProject firstProject, AbstractProject lastProject, List<Stage> stages) { super(name); this.firstProject = firstProject; this.lastProject = lastProject; this.stages = stages; } public Pipeline(String name, AbstractProject firstProject, AbstractProject lastProject, String version, String timestamp, List<TriggerCause> triggeredBy, Set<UserInfo> contributors, List<Stage> stages, boolean aggregated) { super(name); this.firstProject = firstProject; this.lastProject = lastProject; this.version = version; this.triggeredBy = triggeredBy; this.contributors = contributors; this.aggregated = aggregated; this.stages = ImmutableList.copyOf(stages); this.timestamp = timestamp; } public AbstractProject getFirstProject() { return this.firstProject; } public AbstractProject getLastProject() { return this.lastProject; } @Exported public List<Stage> getStages() { return stages; } @Exported public String getVersion() { return version; } @Exported public String getTimestamp() { return timestamp; } @Exported public boolean isAggregated() { return aggregated; } @Exported public Set<UserInfo> getContributors() { return contributors; } @Exported public int getId() { return hashCode(); } public void setChanges(List<Change> changes) { this.changes = changes; } @Exported public List<Change> getChanges() { return changes; } public void setCommits(int commits) { this.commits = commits; } @Exported public long getTotalBuildTime() { return totalBuildTime; } @Exported public int getCommits() { return commits; } public void calculateTotalBuildTime() { if (stages.size() == 0) { this.totalBuildTime = 0L; } else { List<Route> allRoutes = new ArrayList<Route>(); calculatePipelineRoutes(getStages().get(0).getTasks().get(0), null, allRoutes); long maxTime = 0L; for (Route route : allRoutes) { long buildTime = route.getTotalBuildTime(); if (buildTime > maxTime) { maxTime = buildTime; } } this.totalBuildTime = maxTime; } } private Route createRouteAndCopyTasks(final Route route, Task task) { Route currentRoute = new Route(); if (route != null) { currentRoute.setTasks(newArrayList(route.getTasks())); } currentRoute.addTask(task); return currentRoute; } void calculatePipelineRoutes(Task task, final Route route, List<Route> allRoutes) { if (task.getDownstreamTasks() != null && task.getDownstreamTasks().size() > 0) { for (String downstreamTaskName: task.getDownstreamTasks()) { // assume each task only appears once in the pipeline Route currentRoute = createRouteAndCopyTasks(route, task); calculatePipelineRoutes(getTaskFromName(downstreamTaskName), currentRoute, allRoutes); } } else { Route currentRoute = createRouteAndCopyTasks(route, task); allRoutes.add(currentRoute); } } private Task getTaskFromName(String taskName) { if (allTasks == null) { allTasks = new HashMap<String, Task>(); for (Stage stage : stages) { for (Task task : stage.getTasks()) { allTasks.put(task.getId(), task); } } } return allTasks.get(taskName); } /** * Created a pipeline prototype for the supplied first project. */ public static Pipeline extractPipeline(String name, AbstractProject<?, ?> firstProject, AbstractProject<?, ?> lastProject, boolean withUpstream) throws PipelineException { ArrayList<Stage> stages = newArrayList(Stage.extractStages(firstProject, lastProject)); if (withUpstream) { return new DownstreamPipeline(name, firstProject, lastProject, stages); } else { return new Pipeline(name, firstProject, lastProject, stages); } } public static Pipeline extractPipeline(String name, AbstractProject<?, ?> firstProject) throws PipelineException { return extractPipeline(name, firstProject, null, false); } Pipeline createPipelineAggregatedWithoutChangesShown(ItemGroup context) { return createPipelineAggregated(context, false); } Pipeline createPipelineAggregatedWithChangesShown(ItemGroup context) { return createPipelineAggregated(context, true); } public Pipeline createPipelineAggregated(ItemGroup context, boolean showAggregatedChanges) { List<Stage> pipelineStages = new ArrayList<Stage>(); for (Stage stage : getStages()) { pipelineStages.add(stage.createAggregatedStage(context, firstProject)); } if (showAggregatedChanges) { setAggregatedChanges(context, pipelineStages); } return new Pipeline(getName(), firstProject, lastProject, null, null, null, null, pipelineStages, true); } void setAggregatedChanges(ItemGroup context, List<Stage> pipelineStages) { // We use size() - 1 because last stage's changelog can't be calculated against next stage (no such) for (int i = 0; i < pipelineStages.size() - 1; i++) { Stage stage = pipelineStages.get(i); Stage nextStage = pipelineStages.get(i + 1); final AbstractBuild nextBuild = nextStage.getHighestBuild(firstProject, context, Result.SUCCESS); Set<Change> changes = newHashSet(); AbstractBuild build = stage.getHighestBuild(firstProject, context, Result.SUCCESS); for (; build != null && build != nextBuild; build = build.getPreviousBuild()) { changes.addAll(Change.getChanges(build)); } stage.setChanges(changes); } } /** * Populates and return pipelines for the supplied pipeline prototype with the current status. * * @param noOfPipelines number of pipeline instances */ public List<Pipeline> createPipelineLatest(int noOfPipelines, ItemGroup context, boolean pagingEnabled, boolean showChanges, Component component) throws PipelineException { List<Pipeline> result = new ArrayList<Pipeline>(); if (firstProject.isInQueue()) { String pipeLineTimestamp = PipelineUtils.formatTimestamp(firstProject.getQueueItem().getInQueueSince()); List<Stage> pipelineStages = new ArrayList<Stage>(); for (Stage stage : getStages()) { pipelineStages.add(stage.createLatestStage(context, null)); } Pipeline pipelineLatest = new Pipeline(getName(), firstProject, lastProject, "#" + firstProject.getNextBuildNumber(), pipeLineTimestamp, TriggerCause.getTriggeredBy(firstProject, null), null, pipelineStages, false); result.add(pipelineLatest); } int totalNoOfPipelines = firstProject.getBuilds().size(); component.setTotalNoOfPipelines(totalNoOfPipelines); int startIndex = 0; int retrieveSize = noOfPipelines; if (pagingEnabled && !component.isFullScreenView()) { startIndex = (component.getCurrentPage() - 1) * noOfPipelines; retrieveSize = Math.min(totalNoOfPipelines - ((component.getCurrentPage() - 1) * noOfPipelines), noOfPipelines); } Iterator it = firstProject.getBuilds().listIterator(startIndex); result.addAll(getPipelines(it, context, startIndex, retrieveSize, showChanges)); return result; } protected List<AbstractBuild> resolveBuilds(List<AbstractProject> firstProjects) { List<AbstractBuild> builds = new ArrayList<AbstractBuild>(); for (AbstractProject firstProject : firstProjects) { builds.addAll(firstProject.getBuilds()); } Collections.sort(builds, new BuildStartTimeComparator()); return builds; } protected List<Pipeline> getPipelines(Iterator it, ItemGroup context, int startIndex, int retrieveSize, boolean showChanges) throws PipelineException { List<Pipeline> result = new ArrayList<Pipeline>(); for (int i = startIndex; i < (startIndex + retrieveSize) && it.hasNext(); i++) { AbstractBuild firstBuild = (AbstractBuild) it.next(); List<Change> pipelineChanges = Change.getChanges(firstBuild); Set<UserInfo> contributors = showChanges ? UserInfo.getContributors(pipelineChanges) : null; String pipeLineTimestamp = PipelineUtils.formatTimestamp(firstBuild.getTimeInMillis()); List<Stage> pipelineStages = new ArrayList<Stage>(); Pipeline pipeline = this; if (showUpstream()) { pipeline = Pipeline.extractPipeline(getName(), firstBuild.getProject(), lastProject, showUpstream()); } for (Stage stage : pipeline.getStages()) { pipelineStages.add(stage.createLatestStage(context, firstBuild)); } Pipeline pipelineLatest = pipelineOf(firstBuild, lastProject, pipeLineTimestamp, contributors, pipelineStages); if (showChanges) { pipelineLatest.setChanges(pipelineChanges); } pipelineLatest.setCommits(pipelineChanges.size()); pipelineLatest.calculateTotalBuildTime(); result.add(pipelineLatest); } return result; } protected Pipeline pipelineOf(AbstractBuild firstBuild, AbstractProject lastProject, String pipeLineTimestamp, Set<UserInfo> contributors, List<Stage> pipelineStages) { return new Pipeline( getName(), firstBuild.getProject(), lastProject, firstBuild.getDisplayName(), pipeLineTimestamp, TriggerCause.getTriggeredBy(firstBuild.getProject(), firstBuild), contributors, pipelineStages, false); } public boolean showUpstream() { return false; } @Override public String toString() { return toStringHelper(this) .add("id", getId()) .add("name", getName()) .add("version", getVersion()) .add("stages", getStages()) .toString(); } @Exported public List<TriggerCause> getTriggeredBy() { return triggeredBy; } }