[JENKINS-38039] branch ordering (#489)

* An attempt at ordering branches

* Order branches by favorites and then activity

* Fix style issues
This commit is contained in:
Ivan Meredith 2016-09-09 12:18:38 +12:00 committed by GitHub
parent 8cfe6e4962
commit 9647650252
5 changed files with 283 additions and 7 deletions

View File

@ -1,13 +1,18 @@
package io.jenkins.blueocean.rest.impl.pipeline;
import com.google.common.collect.Ordering;
import hudson.model.Job;
import io.jenkins.blueocean.rest.hal.Link;
import io.jenkins.blueocean.rest.model.BluePipeline;
import io.jenkins.blueocean.rest.model.BluePipelineContainer;
import io.jenkins.blueocean.rest.model.BlueRun;
import io.jenkins.blueocean.service.embedded.rest.ContainerFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@ -15,6 +20,95 @@ import java.util.List;
* @author Vivek Pandey
*/
public class BranchContainerImpl extends BluePipelineContainer {
/**
* Orders branches with most recent activity first. Favorited branches are always at the top, also in recent
* order.
*/
private static final Comparator<BluePipeline> BRANCH_COMPARITOR = new Comparator<BluePipeline>() {
@Override
public int compare(BluePipeline _pipeline1, BluePipeline _pipeline2) {
BranchImpl pipeline1 = (BranchImpl)_pipeline1;
BranchImpl pipeline2 = (BranchImpl)_pipeline2;
// If One pipeline isnt a favorite there is no need to go further.
if(pipeline1.isFavorite() && !pipeline2.isFavorite()) {
return -1;
}
if(!pipeline1.isFavorite() && pipeline2.isFavorite()) {
return 1;
}
BlueRun latestRun1 = pipeline1.getLatestRun();
BlueRun latestRun2 = pipeline2.getLatestRun();
// If a pipeline doesnt have a run yet, no need to go further.
if(latestRun1 != null && latestRun2 == null) {
return -1;
}
if(latestRun1 == null && latestRun2 != null) {
return 1;
}
//If neither have runs, lets just order by name.
if(latestRun1 == null && latestRun2 == null) {
return pipeline1.getName().compareTo(pipeline2.getName());
}
// If one run hasnt finished yet, then lets order by that.
Date endTime1 = latestRun1.getEndTime() ;
Date endTime2 = latestRun2.getEndTime();
if(endTime1 != null && endTime2 == null) {
return 1;
}
if(endTime1 == null && endTime2 != null) {
return -1;
}
// If both jobs have ended, lets order by the one that ended last.
if(endTime1 != null && endTime2 != null) {
if(endTime1.getTime() > endTime2.getTime()) {
return -1;
}
if(endTime1.getTime() < endTime2.getTime()) {
return 1;
}
return pipeline1.getName().compareTo(pipeline2.getName());
}
//If both jobs have not eneded yet, we need to order by start time.
Date startTime1 = latestRun1.getStartTime();
Date startTime2 = latestRun2.getStartTime();
if(startTime1 != null && startTime2 == null) {
return 1;
}
if(startTime1 == null && startTime2 != null) {
return -1;
}
if(startTime1 != null && startTime2 != null) {
if(startTime1.getTime() > startTime2.getTime()) {
return -1;
}
if(startTime1.getTime() < startTime2.getTime()) {
return 1;
}
return pipeline1.getName().compareTo(pipeline2.getName());
}
return pipeline1.getName().compareTo(pipeline2.getName());
}
};
private final MultiBranchPipelineImpl pipeline;
private final Link self;
@ -33,6 +127,7 @@ public class BranchContainerImpl extends BluePipelineContainer {
}
@Override
@SuppressWarnings("unchecked")
public Iterator<BluePipeline> iterator() {
List<BluePipeline> branches = new ArrayList<>();
Collection<Job> jobs = pipeline.mbp.getAllJobs();
@ -40,7 +135,8 @@ public class BranchContainerImpl extends BluePipelineContainer {
for(Job j: jobs){
branches.add(new BranchImpl(j, getLink()));
}
return branches.iterator();
return Ordering.from(BRANCH_COMPARITOR).sortedCopy(branches).iterator();
}
@Override

View File

@ -0,0 +1,165 @@
package io.jenkins.blueocean.rest.impl.pipeline;
import com.google.common.collect.ImmutableMap;
import io.jenkins.blueocean.rest.impl.pipeline.scm.GitSampleRepoRule;
import jenkins.branch.BranchProperty;
import jenkins.branch.BranchSource;
import jenkins.branch.DefaultBranchPropertyStrategy;
import jenkins.plugins.git.GitSCMSource;
import jenkins.scm.api.SCMSource;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author Ivan Meredith
*/
public class BranchContainerImplTest extends PipelineBaseTest {
@Rule
public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();
@Before
public void setup() throws Exception{
super.setup();
setupScm();
}
@Test
public void testBranchOrdering() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
hudson.model.User user = j.jenkins.getUser("alice");
user.setFullName("Alice Cooper");
WorkflowMultiBranchProject mp = j.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", false),
new DefaultBranchPropertyStrategy(new BranchProperty[0])));
for (SCMSource source : mp.getSCMSources()) {
assertEquals(mp, source.getOwner());
}
WorkflowJob p = scheduleAndFindBranchProject(mp, "master");
j.waitUntilNoActivity();
String token = getJwtToken(j.jenkins, "alice", "alice");
new RequestBuilder(baseUrl)
.put("/organizations/jenkins/pipelines/p/branches/feature2/favorite")
.jwtToken(token)
.data(ImmutableMap.of("favorite", true))
.build(Map.class);
new RequestBuilder(baseUrl)
.put("/organizations/jenkins/pipelines/p/branches/feature4/favorite")
.jwtToken(token)
.data(ImmutableMap.of("favorite", true))
.build(Map.class);
List l = request().get("/organizations/jenkins/pipelines/p/branches/")
.jwtToken(token)
.build(List.class);
Assert.assertEquals(4,l.size());
Map o = (Map)l.get(1);
Map o2 = (Map)l.get(0);
Assert.assertTrue(o.get("name").equals("feature2") || o.get("name").equals("feature4"));
WorkflowJob j1 = findBranchProject(mp, (String)o.get("name"));
j.waitForCompletion(j1.getLastBuild());
j.waitForCompletion(j1.scheduleBuild2(0).waitForStart());
WorkflowJob j2 = findBranchProject(mp, (String)o2.get("name"));
j.waitForCompletion(j2.getLastBuild());
List l2 = request().get("/organizations/jenkins/pipelines/p/branches/")
.jwtToken(token)
.build(List.class);
Assert.assertEquals(4,l.size());
Map o1 = (Map)l2.get(0);
Map o3 = (Map)l2.get(1);
Assert.assertEquals(o.get("name"), o1.get("name"));
}
private WorkflowJob scheduleAndFindBranchProject(WorkflowMultiBranchProject mp, String name) throws Exception {
mp.scheduleBuild2(0).getFuture().get();
return findBranchProject(mp, name);
}
private void scheduleAndFindBranchProject(WorkflowMultiBranchProject mp) throws Exception {
mp.scheduleBuild2(0).getFuture().get();
}
private WorkflowJob findBranchProject(WorkflowMultiBranchProject mp, String name) throws Exception {
WorkflowJob p = mp.getItem(name);
if (p == null) {
mp.getIndexing().writeWholeLogTo(System.out);
fail(name + " project not found");
}
return p;
}
private void setupScm() throws Exception {
// create git repo
sampleRepo.init();
sampleRepo.write("Jenkinsfile", "stage 'build'\n "+"node {echo 'Building'}\n"+
"stage 'test'\nnode { echo 'Testing'}\n"+
"stage 'deploy'\nnode { echo 'Deploying'}\n"
);
sampleRepo.write("file", "initial content");
sampleRepo.git("add", "Jenkinsfile");
sampleRepo.git("commit", "--all", "--message=flow");
//create feature branch
sampleRepo.git("checkout", "-b", "feature/ux-1");
sampleRepo.write("Jenkinsfile", "echo \"branch=${env.BRANCH_NAME}\"; "+"node {" +
" stage ('Build'); " +
" echo ('Building'); " +
" stage ('Test'); " +
" echo ('Testing'); " +
" stage ('Deploy'); " +
" echo ('Deploying'); " +
"}");
ScriptApproval.get().approveSignature("method java.lang.String toUpperCase");
sampleRepo.write("file", "subsequent content1");
sampleRepo.git("commit", "--all", "--message=tweaked1");
//create feature branch
sampleRepo.git("checkout", "-b", "feature2");
sampleRepo.write("Jenkinsfile", "echo \"branch=${env.BRANCH_NAME}\"; "+"node {" +
" stage ('Build'); " +
" echo ('Building'); " +
" stage ('Test'); " +
" echo ('Testing'); " +
" stage ('Deploy'); " +
" echo ('Deploying'); " +
"}");
ScriptApproval.get().approveSignature("method java.lang.String toUpperCase");
sampleRepo.write("file", "subsequent content2");
sampleRepo.git("commit", "--all", "--message=tweaked2");
sampleRepo.git("checkout", "-b", "feature4");
sampleRepo.write("Jenkinsfile", "echo \"branch=${env.BRANCH_NAME}\"; "+"node {" +
" stage ('Build'); " +
" echo ('Building'); " +
" stage ('Test'); " +
" echo ('Testing'); " +
" stage ('Deploy'); " +
" echo ('Deploying'); " +
"}");
ScriptApproval.get().approveSignature("method java.lang.String toUpperCase");
sampleRepo.write("file", "subsequent content234");
sampleRepo.git("commit", "--all", "--message=tweaked4");
}
}

View File

@ -12,6 +12,8 @@ import hudson.model.BuildableItem;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.User;
import hudson.plugins.favorite.user.FavoriteUserProperty;
import hudson.util.RunList;
import io.jenkins.blueocean.commons.ServiceException;
import io.jenkins.blueocean.rest.Navigable;
@ -245,5 +247,14 @@ public class AbstractPipelineImpl extends BluePipeline {
}
};
public boolean isFavorite() {
User user = User.current();
if(user != null) {
FavoriteUserProperty prop = user.getProperty(FavoriteUserProperty.class);
return prop != null && prop.isJobFavorite(job.getFullName());
}
return false;
}
}

View File

@ -2,6 +2,7 @@ package io.jenkins.blueocean.service.embedded.rest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
@ -23,12 +24,12 @@ public abstract class ContainerFilter implements ExtensionPoint {
* Name to match
*/
public abstract String getName();
/**
* Predicate to filter items
*/
public abstract Predicate<Item> getFilter();
/**
* Filters the item list based on the current StaplerRequest
*/
@ -43,7 +44,7 @@ public abstract class ContainerFilter implements ExtensionPoint {
}
return filter(items, itemFilter.split(","));
}
/**
* Filters the item list based on the supplied filter name
*/
@ -58,7 +59,7 @@ public abstract class ContainerFilter implements ExtensionPoint {
}
filters[i] = f;
}
Collection<T> out = new ArrayList<>();
Collection<T> out = new LinkedList<>();
nextItem: for (T item : items) {
for (int i = 0; i < filters.length; i++) {
if (!filters[i].apply(item)) {
@ -71,7 +72,7 @@ public abstract class ContainerFilter implements ExtensionPoint {
}
return items;
}
/**
* Finds a item filter by name
*/

View File

@ -542,7 +542,10 @@ Each branch in the repo with Jenkins file will appear as a branch in this pipeli
}
### Get MultiBranch pipeline branches
### Get MultiBranch pipeline branches
The list of branches will be ordered by favorited branches first, and then branches that have the most recent
activity.
curl -v http://localhost:56720/jenkins/blue/rest/organizations/jenkins/pipelines/pipeline1/branches