diff --git a/README.markdown b/README.markdown
index 10c2a9c..29c41b0 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,5 +1,4 @@
## HipChat plugin for Jenkins
Started with a fork of the Campfire plugin:
-
https://github.com/jgp/hudson_campfire_plugin
diff --git a/pom.xml b/pom.xml
index c4d4a59..8d65e9e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,12 @@
4.11
test
+
+ org.mockito
+ mockito-all
+ 1.10.8
+ test
+
log4j
log4j
@@ -77,6 +83,10 @@
junit
junit
+
+ org.mockito
+ mockito-all
+
diff --git a/src/main/java/jenkins/plugins/hipchat/ActiveNotifier.java b/src/main/java/jenkins/plugins/hipchat/ActiveNotifier.java
index df84b28..8a97095 100644
--- a/src/main/java/jenkins/plugins/hipchat/ActiveNotifier.java
+++ b/src/main/java/jenkins/plugins/hipchat/ActiveNotifier.java
@@ -1,22 +1,17 @@
package jenkins.plugins.hipchat;
-import hudson.Util;
-import hudson.model.AbstractBuild;
-import hudson.model.AbstractProject;
-import hudson.model.CauseAction;
-import hudson.model.Result;
-import hudson.model.Run;
+import hudson.model.*;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.AffectedFile;
import hudson.scm.ChangeLogSet.Entry;
+import hudson.tasks.Mailer;
+
import org.apache.commons.lang.StringUtils;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
+
import jenkins.model.Jenkins;
@SuppressWarnings("rawtypes")
@@ -31,7 +26,7 @@ public ActiveNotifier(HipChatNotifier notifier) {
this.notifier = notifier;
}
- private HipChatService getHipChat(AbstractBuild r) {
+ private HipChatService getHipChat() {
return notifier.newHipChatService();
}
@@ -49,12 +44,12 @@ public void started(AbstractBuild build) {
message.append(cause.getShortDescription());
notifyStart(build, message.appendOpenLink().toString());
} else {
- notifyStart(build, getBuildStatusMessage(build));
+ notifyStart(build, getBuildStatusMessage(build, null));
}
}
private void notifyStart(AbstractBuild build, String message) {
- getHipChat(build).publish(message, "green");
+ getHipChat().publish(message, "green");
}
public void finalized(AbstractBuild r) {
@@ -71,7 +66,39 @@ public void completed(AbstractBuild r) {
|| (result == Result.SUCCESS && previousResult == Result.FAILURE && notifier.isNotifyBackToNormal())
|| (result == Result.SUCCESS && notifier.isNotifySuccess())
|| (result == Result.UNSTABLE && notifier.isNotifyUnstable())) {
- getHipChat(r).publish(getBuildStatusMessage(r), getBuildColor(r));
+ getHipChat().publish(getBuildStatusMessage(r, fetchCulpritsIfWanted(r)), getBuildColor(r));
+ }
+ }
+
+ private List fetchCulpritsIfWanted(AbstractBuild r) {
+ if (notifier.isIncludeCulprits()) {
+ return getCulpritsInHipchat(r);
+ } else {
+ return null;
+ }
+ }
+
+ private List getCulpritsInHipchat(AbstractBuild r) {
+ List hipchatUsernames = new ArrayList();
+ for(Object userObj : r.getCulprits()) {
+ User user = (User)userObj;
+ logger.log(Level.FINE, "Looking up mention name for user {0}", user);
+ Mailer.UserProperty mailProperty = (user).getProperty(Mailer.UserProperty.class);
+ if(mailProperty != null && !StringUtils.isEmpty(mailProperty.getAddress())) {
+ hipchatUsernames.add(buildMentionNameFromEmail(mailProperty));
+ }else{
+ hipchatUsernames.add(user.getFullName());
+ }
+ }
+ return hipchatUsernames;
+ }
+
+ private String buildMentionNameFromEmail(Mailer.UserProperty mailProperty) {
+ String mentionName = getHipChat().getMentionNameForEmail(mailProperty.getAddress());
+ if (mentionName != null) {
+ return "@" + mentionName;
+ } else {
+ return mailProperty.getAddress();
}
}
@@ -122,10 +149,11 @@ static String getBuildColor(AbstractBuild r) {
}
}
- String getBuildStatusMessage(AbstractBuild r) {
+ String getBuildStatusMessage(AbstractBuild r, List culpritsInHipchat) {
MessageBuilder message = new MessageBuilder(r);
message.appendStatusMessage();
message.appendDuration();
+ message.appendCulprits(culpritsInHipchat);
return message.appendOpenLink().toString();
}
@@ -179,8 +207,11 @@ private MessageBuilder startMessage() {
}
public MessageBuilder appendOpenLink() {
- String url = Jenkins.getInstance().getRootUrl() + build.getUrl();
- message.append(" (Open)");
+ message.append(" (Open)");
return this;
}
@@ -190,6 +221,17 @@ public MessageBuilder appendDuration() {
return this;
}
+ public MessageBuilder appendCulprits(List culprits) {
+ if(culprits != null && culprits.size()>0) {
+ message.append(" Committers: ");
+ for (String hipchatUsername : culprits) {
+ message.append(hipchatUsername);
+ message.append(" ");
+ }
+ }
+ return this;
+ }
+
@Override
public String toString() {
return message.toString();
diff --git a/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java b/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java
index 6cdbb68..8405935 100644
--- a/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java
+++ b/src/main/java/jenkins/plugins/hipchat/HipChatNotifier.java
@@ -37,10 +37,12 @@ public class HipChatNotifier extends Notifier {
private boolean notifyUnstable;
private boolean notifyFailure;
private boolean notifyBackToNormal;
+ private boolean includeCulprits;
@DataBoundConstructor
public HipChatNotifier(String room, boolean startNotification, boolean notifySuccess, boolean notifyAborted,
- boolean notifyNotBuilt, boolean notifyUnstable, boolean notifyFailure, boolean notifyBackToNormal) {
+ boolean notifyNotBuilt, boolean notifyUnstable, boolean notifyFailure, boolean notifyBackToNormal,
+ boolean includeCulprits) {
this.room = room;
this.startNotification = startNotification;
this.notifySuccess = notifySuccess;
@@ -49,6 +51,7 @@ public HipChatNotifier(String room, boolean startNotification, boolean notifySuc
this.notifyUnstable = notifyUnstable;
this.notifyFailure = notifyFailure;
this.notifyBackToNormal = notifyBackToNormal;
+ this.includeCulprits = includeCulprits;
}
public boolean isStartNotification() {
@@ -107,6 +110,14 @@ public void setNotifyBackToNormal(boolean notifyBackToNormal) {
this.notifyBackToNormal = notifyBackToNormal;
}
+ public boolean isIncludeCulprits() {
+ return includeCulprits;
+ }
+
+ public void setIncludeCulprits(boolean includeCulprits) {
+ this.includeCulprits = includeCulprits;
+ }
+
public String getRoom() {
return StringUtils.isBlank(room) ? getDescriptor().getRoom() : room;
}
@@ -151,7 +162,7 @@ public boolean prebuild(AbstractBuild, ?> build, BuildListener listener) {
logger.info("Invoking Started...");
new ActiveNotifier(this).started(build);
}
- return super.prebuild(build, listener);
+ return true;
}
@Override
@@ -159,7 +170,7 @@ public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListen
throws InterruptedException, IOException {
logger.info("Invoking Completed...");
new ActiveNotifier(this).completed(build);
- return super.perform(build, launcher, listener);
+ return true;
}
@Extension
diff --git a/src/main/java/jenkins/plugins/hipchat/HipChatService.java b/src/main/java/jenkins/plugins/hipchat/HipChatService.java
index 320d2b2..795ddc8 100644
--- a/src/main/java/jenkins/plugins/hipchat/HipChatService.java
+++ b/src/main/java/jenkins/plugins/hipchat/HipChatService.java
@@ -4,4 +4,6 @@ public interface HipChatService {
void publish(String message);
void publish(String message, String color);
+
+ String getMentionNameForEmail(String email);
}
diff --git a/src/main/java/jenkins/plugins/hipchat/StandardHipChatService.java b/src/main/java/jenkins/plugins/hipchat/StandardHipChatService.java
index cfb850f..ad1bc52 100644
--- a/src/main/java/jenkins/plugins/hipchat/StandardHipChatService.java
+++ b/src/main/java/jenkins/plugins/hipchat/StandardHipChatService.java
@@ -2,8 +2,12 @@
import hudson.ProxyConfiguration;
import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import java.util.logging.Level;
@@ -14,19 +18,25 @@ public class StandardHipChatService implements HipChatService {
private static final Logger logger = Logger.getLogger(StandardHipChatService.class.getName());
private static final String[] DEFAULT_ROOMS = new String[0];
+ private final HttpClient httpClient;
private final String server;
private final String token;
private final String[] roomIds;
private final String sendAs;
- public StandardHipChatService(String server, String token, String roomIds, String sendAs) {
+ StandardHipChatService(HttpClient httpClient, String server, String token, String roomIds, String sendAs) {
super();
+ this.httpClient = httpClient;
this.server = server;
this.token = token;
this.roomIds = roomIds == null ? DEFAULT_ROOMS : roomIds.split("\\s*,\\s*");
this.sendAs = sendAs;
}
+ public StandardHipChatService(String server, String token, String roomIds, String sendAs) {
+ this(null, server, token, roomIds, sendAs);
+ }
+
public void publish(String message) {
publish(message, "yellow");
}
@@ -58,24 +68,57 @@ public void publish(String message, String color) {
}
}
- private HttpClient getHttpClient() {
- HttpClient client = new HttpClient();
-
- if (Jenkins.getInstance() != null) {
- ProxyConfiguration proxy = Jenkins.getInstance().proxy;
-
- if (proxy != null) {
- client.getHostConfiguration().setProxy(proxy.name, proxy.port);
+ public String getMentionNameForEmail(String email) {
+ HttpClient client = getHttpClient();
+ String url = "https://" + server + "/v1/users/show";
+ GetMethod get = new GetMethod(url);
+ get.setQueryString(new NameValuePair[] {
+ new NameValuePair("user_id", email),
+ new NameValuePair("auth_token", token)
+ });
+
+ try {
+ int responseCode = client.executeMethod(get);
+ if (responseCode == HttpStatus.SC_OK) {
+ return parseMentionNameFromUserResponse(get.getResponseBodyAsString());
+ } else {
+ logger.log(Level.WARNING, "HipChat user lookup has failed with error: {0}", get.getResponseBodyAsString());
+ return null;
}
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Error looking up user from HipChat", e);
+ return null;
+ } finally {
+ get.releaseConnection();
}
+ }
- return client;
+ private HttpClient getHttpClient() {
+ if (httpClient != null) {
+ return httpClient;
+ } else {
+ HttpClient client = new HttpClient();
+
+ if (Jenkins.getInstance() != null) {
+ ProxyConfiguration proxy = Jenkins.getInstance().proxy;
+
+ if (proxy != null) {
+ client.getHostConfiguration().setProxy(proxy.name, proxy.port);
+ }
+ }
+
+ return client;
+ }
}
private String shouldNotify(String color) {
return color.equalsIgnoreCase("green") ? "0" : "1";
}
+ private String parseMentionNameFromUserResponse(String response) {
+ return JSONObject.fromObject(response).getJSONObject("user").getString("mention_name");
+ }
+
public String getServer() {
return server;
}
diff --git a/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly b/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly
index 35ba7d4..a656103 100644
--- a/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly
+++ b/src/main/resources/jenkins/plugins/hipchat/HipChatNotifier/config.jelly
@@ -32,5 +32,9 @@
+
+
+
+
diff --git a/src/test/java/jenkins/plugins/hipchat/ActiveNotifierTest.java b/src/test/java/jenkins/plugins/hipchat/ActiveNotifierTest.java
new file mode 100644
index 0000000..c09f7a1
--- /dev/null
+++ b/src/test/java/jenkins/plugins/hipchat/ActiveNotifierTest.java
@@ -0,0 +1,89 @@
+package jenkins.plugins.hipchat;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import hudson.model.Result;
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
+import hudson.model.User;
+import hudson.tasks.Mailer;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+import static org.hamcrest.CoreMatchers.*;
+
+public class ActiveNotifierTest {
+ private HipChatService hipChatService;
+ private HipChatNotifier hipChatNotifier;
+ private ActiveNotifier activeNotifier;
+
+ @SuppressWarnings("rawtypes")
+ private AbstractProject project;
+ @SuppressWarnings("rawtypes")
+ private AbstractBuild lastBuild;
+ @SuppressWarnings("rawtypes")
+ private AbstractBuild build;
+
+ @Before
+ public void setUp() {
+ hipChatNotifier = mock(HipChatNotifier.class);
+ activeNotifier = new ActiveNotifier(hipChatNotifier);
+
+ hipChatService = mock(HipChatService.class);
+ when(hipChatNotifier.newHipChatService()).thenReturn(hipChatService);
+
+ project = mock(AbstractProject.class);
+ lastBuild = mock(AbstractBuild.class);
+ build = mock(AbstractBuild.class);
+
+ when(build.getProject()).thenReturn(project);
+ when(project.getLastBuild()).thenReturn(lastBuild);
+ }
+
+ @Test
+ public void addCulpritsToPublishedMessageWhenEnabled() {
+ givenTheBuildHasJustFailedAndWereListeningForFailures();
+ givenThereWereThreeCulpritsOneOfWhichIsKnownToHipChat();
+
+ when(hipChatNotifier.isIncludeCulprits()).thenReturn(true);
+ activeNotifier.completed(build);
+
+ thenHipChatNotificationShouldContain("Henrys Cat");
+ thenHipChatNotificationShouldContain("@KnobCat");
+ thenHipChatNotificationShouldContain("grumpy@cat.com");
+ }
+
+ private void givenTheBuildHasJustFailedAndWereListeningForFailures() {
+ when(build.getResult()).thenReturn(Result.FAILURE);
+ when(hipChatNotifier.isNotifyFailure()).thenReturn(true);
+ when(build.getUrl()).thenReturn("http://www.cats.com");
+ }
+
+ private void givenThereWereThreeCulpritsOneOfWhichIsKnownToHipChat() {
+ Set culprits = new HashSet();
+ culprits.add(makeUser("Henrys Cat", null));
+ culprits.add(makeUser("#KnobCat", "knob@cat.com"));
+ culprits.add(makeUser("Grumpy Cat", "grumpy@cat.com"));
+ when(build.getCulprits()).thenReturn(culprits);
+
+ when(hipChatService.getMentionNameForEmail("knob@cat.com")).thenReturn("KnobCat");
+ }
+
+ private void thenHipChatNotificationShouldContain(String contains) {
+ verify(hipChatService).publish(argThat(containsString(contains)), anyString());
+ }
+
+ private User makeUser(String fullName, String email) {
+ User user = mock(User.class);
+ when(user.getFullName()).thenReturn(fullName);
+ if (email != null) {
+ Mailer.UserProperty userProperty = mock(Mailer.UserProperty.class);
+ when(userProperty.getAddress()).thenReturn(email);
+ when(user.getProperty(Mailer.UserProperty.class)).thenReturn(userProperty);
+ }
+ return user;
+ }
+}
diff --git a/src/test/java/jenkins/plugins/hipchat/StandardHipChatServiceTest.java b/src/test/java/jenkins/plugins/hipchat/StandardHipChatServiceTest.java
index 96cbb4d..38d3f07 100644
--- a/src/test/java/jenkins/plugins/hipchat/StandardHipChatServiceTest.java
+++ b/src/test/java/jenkins/plugins/hipchat/StandardHipChatServiceTest.java
@@ -1,11 +1,37 @@
package jenkins.plugins.hipchat;
+import hudson.util.ReflectionUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.junit.Before;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class StandardHipChatServiceTest {
+ private HttpClient httpClient;
+ private StandardHipChatService standardHipChatService;
+
+ @Before
+ public void setUp() {
+ httpClient = mock(HttpClient.class);
+ standardHipChatService = new StandardHipChatService(httpClient, "api.hipchat.com", "token", "room", "from");
+ }
+
@Test
public void publishWithBadHostShouldNotRethrowExceptions() {
StandardHipChatService service = new StandardHipChatService("badhost", "token", "room", "from");
@@ -41,4 +67,60 @@ public void shouldBeAbleToOverrideFrom() {
StandardHipChatService service = new StandardHipChatService(null, "token", "room", "from");
assertEquals("from", service.getSendAs());
}
+
+ @Test
+ public void shouldReturnMentionNameBasedOnEmail() {
+ givenTheRequestForBobGeldofsUserReturnsOk();
+ String mentionName = standardHipChatService.getMentionNameForEmail("bob@geldof.com");
+ assertEquals("BobbieG", mentionName);
+ }
+
+ @Test
+ public void shouldReturnNullMentionNameForInvalidResponse() {
+ givenTheRequestForRodJaneAndFreddyUserReturnsInvalidJson();
+ String mentionName = standardHipChatService.getMentionNameForEmail("rodjanefreddy@rainbow.com");
+ assertNull(mentionName);
+ }
+
+ @Test
+ public void shouldReturnNullMentionNameForErrorResponse() {
+ givenTheRequestForBonoReturnsAnErrorResponse();
+ String mentionName = standardHipChatService.getMentionNameForEmail("bono@therealu2.com");
+ assertNull(mentionName);
+ }
+
+ private void givenTheRequestForBobGeldofsUserReturnsOk() {
+ mockUpHttpClient(HttpStatus.SC_OK, "user_id=bob%40geldof.com&auth_token=token",
+ "{\"user\":{\"mention_name\":\"BobbieG\"}}");
+ }
+
+ private void givenTheRequestForRodJaneAndFreddyUserReturnsInvalidJson() {
+ mockUpHttpClient(HttpStatus.SC_OK, "user_id=rodjanefreddy%40rainbow.com&auth_token=token",
+ "RAAAAAAAAAAA");
+ }
+
+ private void givenTheRequestForBonoReturnsAnErrorResponse() {
+ mockUpHttpClient(HttpStatus.SC_FORBIDDEN, "user_id=bono%40therealu2.com&auth_token=token",
+ "{\"error\":{\"code\":401}");
+ }
+
+ private void mockUpHttpClient(final int responseCode, final String expectedQueryString, final String response) {
+ try {
+ when(httpClient.executeMethod(any(GetMethod.class))).thenAnswer(new Answer() {
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ GetMethod get = invocation.getArgumentAt(0, GetMethod.class);
+ assertEquals(expectedQueryString, get.getQueryString());
+
+ Method method = HttpMethodBase.class.getDeclaredMethod("setResponseStream", InputStream.class);
+ method.setAccessible(true);
+ InputStream in = new ByteArrayInputStream(response.getBytes());
+ ReflectionUtils.invokeMethod(method, get, new Object[] {in});
+
+ return responseCode;
+ }
+ });
+ } catch(Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
}