Skip to content

Commit

Permalink
Merge pull request #12 from drekbour/assign-claim
Browse files Browse the repository at this point in the history
JENKINS-7824 Allow assigning builds/tests to other users
  • Loading branch information
ki82 committed Aug 19, 2014
2 parents c474b1b + cc86aac commit dcefc3d
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 114 deletions.
2 changes: 1 addition & 1 deletion readme.md
@@ -1,7 +1,7 @@
Jenkins Claim Plugin
=========================

A plugin for Jenkins CI that allows users to claim(take responsibility) for a failing build.<br>
A plugin for Jenkins CI that allows users to claim (take responsibility) for a failing build or assign it to another user (aka blame).<br>
Look at [wiki] for detailed instructions.

Change Log
Expand Down
31 changes: 23 additions & 8 deletions src/main/java/hudson/plugins/claim/AbstractClaimBuildAction.java 100644 → 100755
@@ -1,14 +1,17 @@
package hudson.plugins.claim;

import hudson.model.BuildBadgeAction;
import hudson.model.Hudson;
import hudson.model.Describable;
import hudson.model.ProminentProjectAction;
import hudson.model.Saveable;
import hudson.model.Hudson;
import hudson.model.User;
import hudson.tasks.junit.TestAction;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

Expand All @@ -20,10 +23,11 @@
import org.kohsuke.stapler.export.ExportedBean;

@ExportedBean(defaultVisibility = 2)
public abstract class AbstractClaimBuildAction<T extends Saveable> extends TestAction implements BuildBadgeAction,
ProminentProjectAction {
public abstract class AbstractClaimBuildAction<T extends Saveable> extends DescribableTestAction implements BuildBadgeAction,
ProminentProjectAction, Describable<DescribableTestAction> {

private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger("claim-plugin");

private boolean claimed;
private String claimedBy;
Expand All @@ -49,8 +53,19 @@ public String getUrlName() {
public void doClaim(StaplerRequest req, StaplerResponse resp)
throws ServletException, IOException {
Authentication authentication = Hudson.getAuthentication();
String name = authentication.getName();
String reason = (String) req.getSubmittedForm().get("reason");
String name = authentication.getName(); // Default to self-assignment
String assignee = req.getSubmittedForm().getString("assignee");
if (!StringUtils.isEmpty(assignee) && !name.equals(assignee)) {
// Validate the specified assignee.
User resolvedAssignee = User.get(assignee, false, Collections.EMPTY_MAP);
if (resolvedAssignee == null) {
LOGGER.log(Level.WARNING, "Invalid username specified for assignment: {0}", assignee);
resp.forwardToPreviousPage(req);
return;
}
name = assignee;
}
String reason = req.getSubmittedForm().getString("reason");
boolean sticky = req.getSubmittedForm().getBoolean("sticky");
if (StringUtils.isEmpty(reason)) reason = null;
claim(name, reason, sticky);
Expand All @@ -71,7 +86,7 @@ public String getClaimedBy() {
}

public String getClaimedByName() {
User user = User.get(claimedBy, false);
User user = User.get(claimedBy, false,Collections.EMPTY_MAP);
if (user != null) {
return user.getDisplayName();
} else {
Expand Down Expand Up @@ -99,7 +114,7 @@ public void claim(String claimedBy, String reason, boolean sticky) {
/**
* Claim a new Run with the same settings as this one.
*/
public void copyTo(AbstractClaimBuildAction other) {
public void copyTo(AbstractClaimBuildAction<T> other) {
other.claim(getClaimedBy(), getReason(), isSticky());
}

Expand Down
72 changes: 72 additions & 0 deletions src/main/java/hudson/plugins/claim/DescribableTestAction.java
@@ -0,0 +1,72 @@
package hudson.plugins.claim;

import hudson.Extension;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.User;
import hudson.tasks.junit.TestAction;
import hudson.util.ListBoxModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public abstract class DescribableTestAction extends TestAction implements Describable<DescribableTestAction> {

//private static final Logger LOGGER = Logger.getLogger("claim-plugin");

public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public Descriptor<DescribableTestAction> getDescriptor() {
return DESCRIPTOR;
}

private static Comparator<User> comparator = new Comparator<User>() {
public int compare(User c1, User c2) {
return c1.getId().compareTo(c2.getId());
}
};

@Extension
public static final class DescriptorImpl extends Descriptor<DescribableTestAction> {
@Override
public String getDisplayName() { return "Assignee"; }

public ListBoxModel doFillAssigneeItems() {
ListBoxModel items = new ListBoxModel();

// sort in case the users are not already in sort order
// with the current user at the top of the list
String currentUserId = Hudson.getAuthentication().getName();
User currentUser = null;
if (currentUserId != null) {
currentUser = User.get(currentUserId);
}
if (currentUser != null) {
items.add(currentUser.getDisplayName(), currentUser.getId());
}

Collection<User> c = User.getAll();
if (c != null && currentUser != null) {
if (c.contains(currentUser)) {
c.remove(currentUser);
}
}

if (c!= null ) {
List<User> l = new ArrayList<User>(c);
Collections.sort(l, comparator);
for (User u : l) {
items.add(u.getDisplayName(), u.getId());
}
}

return items;

}
}

}
Expand Up @@ -30,6 +30,7 @@
</j:choose>
<j:if test="${it.canRelease()}">
<a id="dropClaim" href="claim/unclaim">${%Drop the claim.}</a>
<st:nbsp/><a id="reassign" href="#" onClick="ShowPopup(this); return false;">${%Reassign the claim.}</a>
</j:if>
</p>
<j:if test="${it.hasReason()}">
Expand All @@ -48,7 +49,7 @@
</j:otherwise>
</j:choose>
<j:if test="${it.canClaim()}">
<a id="claimForYourself" href="#" onClick="ShowPopup(this); return false;">${%Claim for yourself.}</a>
<a id="reassign" href="#" onClick="ShowPopup(this); return false;">${%Reassign the claim.}</a>
</j:if>
</p>
<j:if test="${it.hasReason()}">
Expand All @@ -58,12 +59,17 @@
<j:otherwise>
${%not.claimed(it.noun)}
<j:if test="${it.canClaim()}">
${%claim.it.link("ShowPopup(this); return false;")}
<a id="claim" href="#" onClick="ShowPopup(this); return false;">${%Claim it.}</a>
</j:if>
</j:otherwise>
</j:choose>
<div id="claimHoverPopup" style="display:none; width:500px; z-index:1000; border:1px solid #bbb;">
<j:set var="descriptor" value="${it.descriptor}"/>
<f:form method="post" action="claim/claim" name="claim">
<f:entry title="${%Assignee}" field="assignee" help="/plugin/claim/help-assignee.html">
<f:select />
</f:entry>

<f:entry title="${%Reason}" help="/plugin/claim/help-reason.html">
<f:textarea name="reason" value="${it.reason}"/>
</f:entry>
Expand Down
Expand Up @@ -3,4 +3,4 @@ you.claimed=You claimed this {0}.
one.claimed.on=This {0} was claimed by {1} on
one.claimed=This {0} was claimed by {1}.
not.claimed=This {0} was not claimed.
claim.it.link=<a id ="claim" href="#" onClick="{0}">Claim</a> it.

@@ -1,6 +1,6 @@
# The MIT License
#
# Copyright (c) 2013, Chunghwa Telecom Co., Ltd., Pei-Tang Huang
# Copyright (c) 2014, Chunghwa Telecom Co., Ltd., Pei-Tang Huang
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,12 +24,13 @@ you.claimed.on=\u60a8\u8a8d\u4e86\u672c{0}\u7684\u5e33\uff0c\u8a18\u9304\u6642\u
.=\u3002
you.claimed=\u60a8\u8a8d\u4e86\u672c{0}\u7684\u5e33\u3002
Drop\ the\ claim.=\u4e0d\u518d\u8a8d\u9019\u7b46\u5e33\u3002
Reassign\ the\ claim.=\u91cd\u7b97\u9019\u7b46\u5e33\u3002
Reason\:=\u539f\u56e0:
one.claimed.on=\u672c{0}\u662f\u7531 {1} \u8a8d\u7684\u5e33\uff0c\u8a18\u9304\u6642\u9593
one.claimed=\u672c{0}\u662f\u7531 {1} \u8a8d\u7684\u5e33\u3002
Claim\ for\ yourself.=\u63db\u6211\u8a8d\u9019\u7b46\u5e33\u3002
not.claimed=\u9019\u500b{0}\u6c92\u4eba\u8a8d\u5e33\u3002
claim.it.link=<a id ="claim" href="#" onClick="{0}">\u8a8d\u9019\u7b46\u5e33</a>\u3002
Claim\ it.=\u8a8d\u9019\u7b46\u5e33\u3002
Assignee=\u4e8b\u4e3b
Reason=\u539f\u56e0
Sticky=\u81ea\u9ecf
Claim=\u8a8d\u5e33
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/index.jelly
@@ -1,3 +1,3 @@
<div>
Allow users to claim failed builds.
Allow users to claim or be assigned failed builds.
</div>
3 changes: 3 additions & 0 deletions src/main/webapp/help-assignee.html
@@ -0,0 +1,3 @@
<div>
The user you are assigning the claim to. Blank means yourself.
</div>
3 changes: 3 additions & 0 deletions src/main/webapp/help-assignee_zh_TW.html
@@ -0,0 +1,3 @@
<div>
指定誰要認這筆帳。都不填代表由您一肩扛起!
</div>
74 changes: 40 additions & 34 deletions src/test/java/hudson/plugins/claim/ClaimReportTest.java
@@ -1,66 +1,72 @@
package hudson.plugins.claim;

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.FreeStyleProject;
import hudson.model.ListView;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.JenkinsRule;

import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import java.io.IOException;
import java.util.concurrent.ExecutionException;

public class ClaimReportTest extends HudsonTestCase {
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class ClaimReportTest {

private static final String JOB_NAME = "job";
@Rule
public JenkinsRule j = new JenkinsRule();


private static final String JOB_NAME = "myjob";
private FreeStyleProject job;
private ListView view;

@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setUp() throws Exception {

java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(java.util.logging.Level.SEVERE);

job = createFailingJobWithName(JOB_NAME);

view = new ListView("DefaultView");
j.jenkins.addView(view);
j.jenkins.setPrimaryView(view);

}

private FreeStyleProject createFailingJobWithName(String jobName) throws IOException,
InterruptedException, ExecutionException {
FreeStyleProject project = createFreeStyleProject(jobName);
InterruptedException, ExecutionException {
FreeStyleProject project = j.createFreeStyleProject(jobName);
project.getBuildersList().add(new FailureBuilder());
project.getPublishersList().add(new ClaimPublisher());
project.scheduleBuild2(0).get();
return project;
}

public void testThatJobNotPresentInDefaultViewIsVisibleInClaimReport() throws Exception {
ListView view = new ListView("DefaultView");
hudson.addView(view);
hudson.setPrimaryView(view);

WebClient wc = new WebClient();

HtmlPage page = wc.goTo("claims/");
@Test
public void job_is_visible_in_claim_report() throws Exception {
// Given:
view.add(job);
//j.interactiveBreak();
// When:
HtmlPage page = j.createWebClient().goTo("claims/");
// Then:
HtmlElement element = page.getElementById("claim.build." + JOB_NAME);
assertTrue(element.isDisplayed());
assertThat(element.isDisplayed(), is(true));
}

public void testJobPresentInDefaultViewIsVisibleInClaimReport() throws Exception {
ListView view = new ListView("DefaultView");
view.add(job);
hudson.addView(view);
hudson.setPrimaryView(view);

WebClient wc = new WebClient();

HtmlPage page = wc.goTo("claims/");
@Test
public void job_not_present_in_default_view_is_visible_in_claim_report() throws Exception {
// When:
HtmlPage page = j.createWebClient().goTo("claims/");
// Then:
HtmlElement element = page.getElementById("claim.build." + JOB_NAME);
assertTrue(element.isDisplayed());
assertThat(element.isDisplayed(), is(true));
}


}

0 comments on commit dcefc3d

Please sign in to comment.