[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:
parent
8cfe6e4962
commit
9647650252
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue