Skip to content

Commit

Permalink
Feature: include/exclude branches of multi-branch pipelines
Browse files Browse the repository at this point in the history
Closes #246
  • Loading branch information
reftel committed Jun 9, 2017
1 parent 4df2499 commit 2d42f2c
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 6 deletions.
2 changes: 1 addition & 1 deletion build-monitor-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>cloudbees-folder</artifactId>
<version>5.16</version>
<version>5.12</version>
<optional>true</optional>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ public String currentOrder() {
return currentConfig().getOrder().getClass().getSimpleName();
}

@SuppressWarnings("unused") // used in the configure-entries.jelly form
public String branchesToInclude() {
return currentConfig().getBranchesToInclude();
}

@SuppressWarnings("unused") // used in the configure-entries.jelly form
public String branchesToExclude() {
return currentConfig().getBranchesToExclude();
}

@SuppressWarnings("unused") // used in the configure-entries.jelly form
public boolean isDisplayCommitters() {
return currentConfig().shouldDisplayCommitters();
Expand All @@ -112,9 +122,13 @@ protected void submit(StaplerRequest req) throws ServletException, IOException,
synchronized (this) {

String requestedOrdering = req.getParameter("order");
String branchesToInclude = req.getParameter("branchesToInclude");
String branchesToExclude = req.getParameter("branchesToExclude");
title = req.getParameter("title");

currentConfig().setDisplayCommitters(json.optBoolean("displayCommitters", true));
currentConfig().setBranchesToInclude(branchesToInclude);
currentConfig().setBranchesToExclude(branchesToExclude);

try {
currentConfig().setOrder(orderIn(requestedOrdering));
Expand Down Expand Up @@ -144,13 +158,10 @@ private boolean isGiven(String value) {
private List<JobView> jobViews() {
JobViews views = new JobViews(new StaticJenkinsAPIs(), currentConfig());

//A little bit of evil to make the type system happy.
@SuppressWarnings("unchecked")
List<Job<?, ?>> projects = new ArrayList(filter(super.getItems(), Job.class));
List<JobView> jobs = new ArrayList<JobView>();

List<Job<?, ?>> projects = currentJobFilter().filterJobs(this.getItems());
Collections.sort(projects, currentConfig().getOrder());

List<JobView> jobs = new ArrayList<JobView>(projects.size());
for (Job project : projects) {
jobs.add(views.viewOf(project));
}
Expand Down Expand Up @@ -179,6 +190,10 @@ else if (deserailisingFromAnOlderFormat()) {
return config;
}

private JobFilter currentJobFilter() {
return new JobFilter(currentConfig());
}

private boolean creatingAFreshView() {
return config == null && order == null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,32 @@ public void setDisplayCommitters(boolean flag) {
public String toString() {
return Objects.toStringHelper(this)
.add("order", order.getClass().getSimpleName())
.add("branchesToInclude", branchesToInclude)
.add("branchesToExclude", branchesToExclude)
.toString();
}

// --

private Comparator<Job<?, ?>> order;

private String branchesToInclude;

public String getBranchesToInclude() {
return branchesToInclude;
}

public void setBranchesToInclude(String branchesToInclude) {
this.branchesToInclude = branchesToInclude;
}

private String branchesToExclude;

public String getBranchesToExclude() {
return branchesToExclude;
}

public void setBranchesToExclude(String branchesToExclude) {
this.branchesToExclude = branchesToExclude;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor;

import com.google.common.base.Strings;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.TopLevelItem;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

import static hudson.Util.filter;

public class JobFilter {
private final Pattern includePattern;
private final Pattern excludePattern;
private Class<? extends ItemGroup> abstractFolderClass;

public JobFilter(Config config) {
String include = config.getBranchesToInclude();
String exclude = config.getBranchesToExclude();
includePattern = Strings.isNullOrEmpty(include) ? null : Pattern.compile(include);
excludePattern = Strings.isNullOrEmpty(exclude) ? null : Pattern.compile(exclude);
try {
abstractFolderClass = Class.forName("com.cloudbees.hudson.plugins.folder.AbstractFolder")
.asSubclass(ItemGroup.class);
} catch (ClassNotFoundException e) {
abstractFolderClass = null;
}
}

public List<Job<?, ?>> filterJobs(Collection<? extends Item> items) {
List<Job<?, ?>> jobs = new ArrayList<Job<?, ?>>();

if (items == null) {
return jobs;
}

if (abstractFolderClass != null) {
for (ItemGroup<?> folder : filter(items, abstractFolderClass)) {
Collection<?> folderItems = folder.getItems();
if (folderItems == null) {
continue;
}
List<Job> groupJobs = filter(folderItems, Job.class);
for (Job job : groupJobs) {
String relativename = job.getRelativeNameFrom(folder);
boolean shouldInclude = includePattern == null || includePattern.matcher(relativename).find();
boolean shouldExclude = excludePattern != null && excludePattern.matcher(relativename).find();
if (shouldInclude && !shouldExclude) {
jobs.add(job);
}
}
}
}

for (Job job : filter(items, Job.class)) {
jobs.add(job);
}

return jobs;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@
<f:textbox name="title" value="${it.title}"/>
</f:entry>

<f:entry title="${%Branches to Include}" help="${descriptor.getHelpFile('branchesToInclude')}">
<f:textbox name="branchesToInclude" field="branchesToInclude" value="${it.branchesToInclude()}"/>
</f:entry>

<f:entry title="${%Branches to Exclude}" help="${descriptor.getHelpFile('branchesToExclude')}">
<f:textbox name="branchesToExclude" field="branchesToExclude" value="${it.branchesToExclude()}"/>
</f:entry>

<f:entry title="${%Ordered by}">
<select name="order" class="setting-input">
<f:option value="ByName" selected='${it.currentOrder()=="ByName"}'>${%Name}</f:option>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div>
<p>
The Exclude Branches setting controls which branches of a multi-branch build to hide.
If set, branches whose names match the value will be excluded, even if they match the Include Branches setting.
If not set, no branches are excluded.
</p>
<p>
Matching is done by treating the value as an unanchored regular expression.
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div>
<p>
The Include Branches setting controls which branches of a multi-branch build to include on the screen.
If set, only branches whose names match the value will be included. If not set, all branches are included.
</p>
<p>
Matching is done by treating the value as an unanchored regular expression.
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor;

import com.cloudbees.hudson.plugins.folder.AbstractFolder;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.TopLevelItem;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class JobFilterTest {
@Test
public void shouldHandleNull() {
JobFilter filter = configuredJobFilter(null, null);
List<Job<?, ?>> jobs = filter.filterJobs(null);
assertThat("should be empty", jobs.isEmpty(), is(true));
}

@Test
public void shouldHandleFolderWithNullItems() {
JobFilter filter = configuredJobFilter(null, "nope");
final AbstractFolder folder = mockedFolderWithNullItems();
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
assertThat("should be empty", jobs.isEmpty(), is(true));
}

@Test
public void shouldNotAddStuff() {
JobFilter filter = configuredJobFilter(null, null);
List<Job<?, ?>> jobs = filter.filterJobs(Collections.<TopLevelItem>emptySet());
assertThat("should be empty", jobs.isEmpty(), is(true));
}

@Test
public void shouldCopyDirectJobs() {
JobFilter filter = configuredJobFilter(null, null);
final Job<?, ?> job = mockedJob("foo");
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(job));
assertThat("should contain one element", jobs.size(), is(1));
assertThat(jobs.get(0), jobIsSame(job));
}

@Test
public void shouldIncludeJobsOfFoldersByDefault() {
JobFilter filter = configuredJobFilter(null, null);
final Job<?, ?> job = mockedJob("job");
final AbstractFolder folder = mockedFolder(job);
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
assertThat("should contain one element", jobs.size(), is(1));
assertThat(jobs.get(0), jobIsSame(job));
}

@Test
public void shouldNotIncludeJobsThatDontMatchTheIncludePattern() {
JobFilter filter = configuredJobFilter("special", null);
final Job<?, ?> job = mockedJob("job");
final AbstractFolder folder = mockedFolder(job);
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
assertThat("should be empty", jobs.isEmpty(), is(true));
}

@Test
public void shouldExcludeMatchingJobs() {
JobFilter filter = configuredJobFilter(null, "nope");
final Job<?, ?> job = mockedJob("nope");
final AbstractFolder folder = mockedFolder(job);
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
assertThat("should be empty", jobs.isEmpty(), is(true));
}

@Test
public void excludeShouldWin() {
JobFilter filter = configuredJobFilter("a", "b");
final Job<?, ?> job = mockedJob("ab");
final AbstractFolder folder = mockedFolder(job);
List<Job<?, ?>> jobs = filter.filterJobs(Collections.singleton(folder));
assertThat("should be empty", jobs.isEmpty(), is(true));
}

private Job<?, ?> mockedJob(String relativeName) {
final Job<?, ?> job = mock(Job.class);
when(job.getRelativeNameFrom((ItemGroup) anyObject())).thenReturn(relativeName);
return job;
}

private AbstractFolder mockedFolder(Job<?, ?>... jobs) {
final AbstractFolder folder = mock(AbstractFolder.class);
when(folder.getItems()).thenReturn(Arrays.asList(jobs));
return folder;
}

private AbstractFolder mockedFolderWithNullItems() {
final AbstractFolder folder = mock(AbstractFolder.class);
when(folder.getItems()).thenReturn(null);
return folder;
}

private JobFilter configuredJobFilter(String include, String exclude) {
Config config = new Config();
config.setBranchesToInclude(include);
config.setBranchesToExclude(exclude);
return new JobFilter(config);
}

private static Matcher<Job> jobIsSame(Job job) {
return new JobIsSame(job);
}

static class JobIsSame extends BaseMatcher<Job> {
private final Job<?, ?> job;

JobIsSame(Job<?, ?> job) {
this.job = job;
}

@Override
public void describeTo(Description description) {
description.appendText("object was not the same as the expected one");
}

@Override
public boolean matches(Object item) {
return item == job;
}
}
}

0 comments on commit 2d42f2c

Please sign in to comment.