diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSenderCause.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSenderCause.java new file mode 100644 index 000000000..c8063c441 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSenderCause.java @@ -0,0 +1,102 @@ +package org.jenkinsci.plugins.github_branch_source; + +import hudson.model.Cause; +import java.util.Objects; +import org.kohsuke.stapler.export.Exported; + +public final class GitHubSenderCause extends Cause { + private final long id; + private final String login; + private final String name; + private final Kind kind; + + GitHubSenderCause(long id, String login, String name, Kind kind) { + this.id = id; + this.login = login; + this.name = name; + this.kind = kind; + } + + @Exported(visibility = 3) + public long getId() { + return id; + } + + @Exported(visibility = 3) + public String getLogin() { + return login; + } + + @Exported(visibility = 3) + public String getName() { + return name; + } + + @Exported(visibility = 3) + public Kind getKind() { + return kind; + } + + @Override + public String getShortDescription() { + String user; + if (name == null) { + user = login; + } else { + user = String.format("%s (%s)", new Object[] {name, login}); + } + return String.format("Caused by GitHub event \"%s\" due to user %s", new Object[] {kind, user}); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + GitHubSenderCause cause = (GitHubSenderCause) obj; + return id == cause.id + && stringEquals(login, cause.login) + && stringEquals(name, cause.name) + && kind == cause.kind; + } + + @Override + public int hashCode() { + return Objects.hash(id, login, name, kind); + } + + private boolean stringEquals(String a, String b) { + if (a == null) { + return b == null; + } + return a.equals(b); + } + + @Override + public String toString() { + return String.format( + "[id=%s] [login=%s] [name=%s] [kind=%s]", new Object[] {id, login, name, kind.name()}); + } + + public enum Kind { + BRANCH_CREATED("branch created"), + BRANCH_DELETED("branch deleted"), + BRANCH_UPDATED("branch updated"), + PULL_REQUEST_CREATED("pull request created"), + PULL_REQUEST_UPDATED("pull request updated"), + PULL_REQUEST_DELETED("pull request deleted"), + PULL_REQUEST_OTHER("pull request"); + + private String description; + + Kind(String description) { + this.description = description; + } + + public String toString() { + return description; + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java index d543b75f1..9f6d721e9 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java @@ -30,6 +30,7 @@ import com.cloudbees.jenkins.GitHubRepositoryName; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.model.Cause; import hudson.model.Item; import hudson.scm.SCM; import java.io.IOException; @@ -57,6 +58,7 @@ import jenkins.scm.api.SCMSourceOwner; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import jenkins.scm.api.trait.SCMHeadPrefilter; +import org.apache.commons.lang3.ArrayUtils; import org.jenkinsci.plugins.github.extension.GHEventsSubscriber; import org.jenkinsci.plugins.github.extension.GHSubscriberEvent; import org.kohsuke.github.GHEvent; @@ -188,6 +190,36 @@ private boolean isApiMatch(String apiUri) { return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); } + @Override + public Cause[] asCauses() { + Cause[] causes = super.asCauses(); + long senderId = getPayload().getSender().getId(); + String senderLogin = getPayload().getSender().getLogin(); + String senderName = null; + try { + senderName = getPayload().getSender().getName(); + } catch (IOException e) { + LOGGER.warning("couldn't determine sender name"); + } + GitHubSenderCause.Kind kind; + String action = getPayload().getAction(); + if ("opened".equals(action)) { + kind = GitHubSenderCause.Kind.PULL_REQUEST_CREATED; + } else if ("reopened".equals(action) + || "synchronize".equals(action) + || "edited".equals(action)) { + kind = GitHubSenderCause.Kind.PULL_REQUEST_UPDATED; + } else if ("closed".equals(action)) { + kind = GitHubSenderCause.Kind.PULL_REQUEST_DELETED; + } else { + kind = GitHubSenderCause.Kind.PULL_REQUEST_OTHER; + } + causes = + ArrayUtils.addAll( + causes, new Cause[] {new GitHubSenderCause(senderId, senderLogin, senderName, kind)}); + return causes; + } + @Override public boolean isMatch(@NonNull SCMNavigator navigator) { return navigator instanceof GitHubSCMNavigator diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java index dae47510c..966befb3a 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java @@ -30,8 +30,10 @@ import com.cloudbees.jenkins.GitHubRepositoryName; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.model.Cause; import hudson.model.Item; import hudson.scm.SCM; +import java.io.IOException; import java.io.StringReader; import java.util.Collections; import java.util.Map; @@ -54,6 +56,7 @@ import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceOwner; import jenkins.scm.api.trait.SCMHeadPrefilter; +import org.apache.commons.lang3.ArrayUtils; import org.jenkinsci.plugins.github.extension.GHEventsSubscriber; import org.jenkinsci.plugins.github.extension.GHSubscriberEvent; import org.kohsuke.github.GHEvent; @@ -178,19 +181,44 @@ private static class SCMHeadEventImpl extends SCMHeadEvent public SCMHeadEventImpl( Type type, long timestamp, - GHEventPayload.Push pullRequest, + GHEventPayload.Push push, GitHubRepositoryName repo, String origin) { - super(type, timestamp, pullRequest, origin); + super(type, timestamp, push, origin); this.repoHost = repo.getHost(); - this.repoOwner = pullRequest.getRepository().getOwnerName(); - this.repository = pullRequest.getRepository().getName(); + this.repoOwner = push.getRepository().getOwnerName(); + this.repository = push.getRepository().getName(); } private boolean isApiMatch(String apiUri) { return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); } + @Override + public Cause[] asCauses() { + Cause[] causes = super.asCauses(); + long senderId = getPayload().getSender().getId(); + String senderLogin = getPayload().getSender().getLogin(); + String senderName = null; + try { + senderName = getPayload().getSender().getName(); + } catch (IOException e) { + LOGGER.warning("couldn't determine sender name"); + } + GitHubSenderCause.Kind kind; + if (getPayload().isCreated()) { + kind = GitHubSenderCause.Kind.BRANCH_CREATED; + } else if (getPayload().isDeleted()) { + kind = GitHubSenderCause.Kind.BRANCH_DELETED; + } else { + kind = GitHubSenderCause.Kind.BRANCH_UPDATED; + } + causes = + ArrayUtils.addAll( + causes, new Cause[] {new GitHubSenderCause(senderId, senderLogin, senderName, kind)}); + return causes; + } + /** {@inheritDoc} */ @Override public boolean isMatch(@NonNull SCMNavigator navigator) { diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java index 7416da7f4..54e5b159e 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertEquals; +import hudson.model.Cause; import java.io.IOException; import java.util.concurrent.TimeUnit; import jenkins.scm.api.SCMEvent; @@ -52,6 +53,7 @@ public class EventsTest { private static SCMEvent.Type firedEventType; private static GHSubscriberEvent ghEvent; + private static Cause[] firedEventCauses; @BeforeClass public static void setupDelay() { @@ -61,8 +63,10 @@ public static void setupDelay() { @Before public void resetFiredEvent() { firedEventType = null; + firedEventCauses = null; ghEvent = null; TestSCMEventListener.setReceived(false); + TestSCMEventListener.setError(null); } @AfterClass @@ -75,6 +79,11 @@ public void given_ghPushEventCreated_then_createdHeadEventFired() throws Excepti PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); firedEventType = SCMEvent.Type.CREATED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.BRANCH_CREATED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pushEventCreated.json"); waitAndAssertReceived(true); } @@ -84,6 +93,11 @@ public void given_ghPushEventDeleted_then_removedHeadEventFired() throws Excepti PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); firedEventType = SCMEvent.Type.REMOVED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.BRANCH_DELETED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pushEventRemoved.json"); waitAndAssertReceived(true); } @@ -93,6 +107,11 @@ public void given_ghPushEventUpdated_then_updatedHeadEventFired() throws Excepti PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); firedEventType = SCMEvent.Type.UPDATED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.BRANCH_UPDATED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pushEventUpdated.json"); waitAndAssertReceived(true); } @@ -102,6 +121,11 @@ public void given_ghPullRequestEventOpened_then_createdHeadEventFired() throws E PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); firedEventType = SCMEvent.Type.CREATED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.PULL_REQUEST_CREATED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventCreated.json"); waitAndAssertReceived(true); } @@ -111,6 +135,11 @@ public void given_ghPullRequestEventClosed_then_removedHeadEventFired() throws E PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); firedEventType = SCMEvent.Type.REMOVED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.PULL_REQUEST_DELETED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventRemoved.json"); waitAndAssertReceived(true); } @@ -120,6 +149,11 @@ public void given_ghPullRequestEventReopened_then_updatedHeadEventFired() throws PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); firedEventType = SCMEvent.Type.UPDATED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.PULL_REQUEST_UPDATED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdated.json"); waitAndAssertReceived(true); } @@ -129,6 +163,11 @@ public void given_ghPullRequestEventSync_then_updatedHeadEventFired() throws Exc PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); firedEventType = SCMEvent.Type.UPDATED; + firedEventCauses = + new Cause[] { + new GitHubSenderCause( + 6752317, "baxterthehacker", null, GitHubSenderCause.Kind.PULL_REQUEST_UPDATED) + }; ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedSync.json"); waitAndAssertReceived(true); } @@ -139,6 +178,7 @@ public void given_ghRepositoryEventCreatedFromFork_then_createdSourceEventFired( GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); firedEventType = SCMEvent.Type.CREATED; + firedEventCauses = new Cause[] {}; ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventCreated.json"); waitAndAssertReceived(true); } @@ -194,26 +234,35 @@ private void waitAndAssertReceived(boolean received) throws InterruptedException "Event should have " + ((!received) ? "not " : "") + "been received", received, TestSCMEventListener.didReceive()); + if (TestSCMEventListener.didError() != null) { + throw TestSCMEventListener.didError(); + } } @TestExtension public static class TestSCMEventListener extends jenkins.scm.api.SCMEventListener { private static boolean eventReceived = false; + private static Error error = null; public void onSCMHeadEvent(SCMHeadEvent event) { - receiveEvent(event.getType(), event.getOrigin()); + receiveEvent(event.getType(), event.getOrigin(), event.asCauses()); } public void onSCMSourceEvent(SCMSourceEvent event) { - receiveEvent(event.getType(), event.getOrigin()); + receiveEvent(event.getType(), event.getOrigin(), event.asCauses()); } - private void receiveEvent(SCMEvent.Type type, String origin) { + private void receiveEvent(SCMEvent.Type type, String origin, Cause[] causes) { eventReceived = true; - assertEquals("Event type should be the same", type, firedEventType); - assertEquals("Event origin should be the same", origin, ghEvent.getOrigin()); + try { + assertEquals("Event type should be the same", type, firedEventType); + assertEquals("Event causes should be the same", causes, firedEventCauses); + assertEquals("Event origin should be the same", origin, ghEvent.getOrigin()); + } catch (Error e) { + error = e; + } } public static boolean didReceive() { @@ -223,5 +272,13 @@ public static boolean didReceive() { public static void setReceived(boolean received) { eventReceived = received; } + + public static Error didError() { + return error; + } + + public static void setError(Error e) { + error = e; + } } }