Skip to content
This repository has been archived by the owner on Jun 9, 2021. It is now read-only.

Commit

Permalink
Trigger Notification Button on Pull Request View #33
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasbjerre committed Aug 9, 2015
1 parent bc2820e commit a3854c7
Show file tree
Hide file tree
Showing 22 changed files with 945 additions and 222 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Changelog of Pull Request Notifier for Stash.
## 1.18
* Avoiding endless loop if user not 'System Admin' when editing configuration
* Triggers can be named. To make it easier to keep track of them in large installations.
* Trigger Notification Buttons on Pull Request View
* And ${BUTTON_TRIGGER_TITLE} variable resolving to title of pressed button
* Building against latest Stash version (3.11.1) using latest Atlassian Maven Plugin Suite version (6.0.3)
* Adding PULL_REQUEST_URL variable. Points to the pull request view in Stash.

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ The Pull Request Notifier for Stash can:
* Invoke any URL, or set of URL:s, when a pull request event happens.
* With variables available to add necessary parameters.
* HTTP POST, PUT, GET and DELETE. POST and PUT also supports rendered post content.
* Be configured to trigger on any [pull request event](https://developer.atlassian.com/static/javadoc/stash/3.10.0/api/reference/com/atlassian/stash/event/pull/package-summary.html). Including source branch change (RESCOPED_FROM) and target branch change (RESCOPED_TO).
* Be configured to trigger on any [pull request event](https://developer.atlassian.com/static/javadoc/stash/3.10.0/api/reference/com/atlassian/stash/event/pull/package-summary.html). Including extended events:
* RESCOPED_FROM, when source branch change
* RESCOPED_TO, when target branch change
* MANUAL_TRIGGER, when trigger button in pull request view is pressed
* Be configured to only trigger if the pull request mathches a filter. A filter text is constructed with any combination of the variables and then a regexp is constructed to match that text.
* Add buttons to pull request view in Stash. And map those buttons to URL invocations. This can be done by setting the filter string to ${BUTTON_TRIGGER_TITLE} and the filter regexp to title of button.
* Authenticate with HTTP basic authentication.
* Send custom HTTP headers
* Can optionally use proxy to connect
Expand All @@ -34,6 +38,7 @@ The filter text as well as the URL support variables. These are:
* ${PULL_REQUEST_VERSION} Example: 1
* ${PULL_REQUEST_COMMENT_TEXT} Example: A comment
* ${PULL_REQUEST_ACTION} Example: OPENED
* ${BUTTON_TRIGGER_TITLE} Example: Trigger Notification
* ${PULL_REQUEST_URL} Example: http://localhost:7990/projects/PROJECT_1/repos/rep_1/pull-requests/1
* ${PULL_REQUEST_USER_DISPLAY_NAME} Example: Some User
* ${PULL_REQUEST_USER_EMAIL_ADDRESS} Example: [email protected]
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
<artifactId>stash-scm-git-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.plugins</groupId>
<artifactId>atlassian-plugins-webfragment</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
Expand Down
156 changes: 156 additions & 0 deletions src/main/java/se/bjurr/prnfs/ManualResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package se.bjurr.prnfs;

import static com.atlassian.stash.user.Permission.ADMIN;
import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.Response.ok;
import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
import static se.bjurr.prnfs.admin.AdminFormValues.BUTTON_VISIBILITY.EVERYONE;
import static se.bjurr.prnfs.admin.AdminFormValues.BUTTON_VISIBILITY.SYSTEM_ADMIN;
import static se.bjurr.prnfs.listener.PrnfsPullRequestAction.BUTTON_TRIGGER;
import static se.bjurr.prnfs.listener.PrnfsRenderer.PrnfsVariable.BUTTON_TRIGGER_TITLE;
import static se.bjurr.prnfs.settings.SettingsStorage.getPrnfsSettings;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import se.bjurr.prnfs.admin.AdminFormValues;
import se.bjurr.prnfs.admin.AdminFormValues.BUTTON_VISIBILITY;
import se.bjurr.prnfs.listener.PrnfsPullRequestAction;
import se.bjurr.prnfs.listener.PrnfsPullRequestEventListener;
import se.bjurr.prnfs.listener.PrnfsRenderer;
import se.bjurr.prnfs.listener.PrnfsRenderer.PrnfsVariable;
import se.bjurr.prnfs.settings.PrnfsButton;
import se.bjurr.prnfs.settings.PrnfsNotification;
import se.bjurr.prnfs.settings.PrnfsSettings;

import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.atlassian.sal.api.user.UserKey;
import com.atlassian.sal.api.user.UserManager;
import com.atlassian.stash.pull.PullRequest;
import com.atlassian.stash.pull.PullRequestService;
import com.atlassian.stash.repository.RepositoryService;
import com.atlassian.stash.server.ApplicationPropertiesService;
import com.atlassian.stash.user.SecurityService;
import com.atlassian.stash.user.StashUser;
import com.atlassian.stash.user.UserService;
import com.atlassian.stash.util.Operation;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.gson.Gson;

@Path("/manual")
public class ManualResource {
private static Gson gson = new Gson();
private final UserManager userManager;
private final UserService userService;
private final PullRequestService pullRequestService;
private final PrnfsPullRequestEventListener prnfsPullRequestEventListener;
private final SecurityService securityService;
private final PluginSettingsFactory pluginSettingsFactory;
private static final List<AdminFormValues.BUTTON_VISIBILITY> adminOk = newArrayList();
private static final List<AdminFormValues.BUTTON_VISIBILITY> systemAdminOk = newArrayList();
static {
adminOk.add(BUTTON_VISIBILITY.ADMIN);
adminOk.add(SYSTEM_ADMIN);
systemAdminOk.add(SYSTEM_ADMIN);
}

public ManualResource(UserManager userManager, UserService userService, PluginSettingsFactory pluginSettingsFactory,
PullRequestService pullRequestService, PrnfsPullRequestEventListener prnfsPullRequestEventListener,
RepositoryService repositoryService, ApplicationPropertiesService propertiesService, SecurityService securityService) {
this.userManager = userManager;
this.userService = userService;
this.pullRequestService = pullRequestService;
this.prnfsPullRequestEventListener = prnfsPullRequestEventListener;
this.securityService = securityService;
this.pluginSettingsFactory = pluginSettingsFactory;
}

@GET
@Produces(APPLICATION_JSON)
public Response get(@Context HttpServletRequest request) throws Exception {
if (userManager.getRemoteUser(request) == null) {
return status(UNAUTHORIZED).build();
}
List<PrnfsButton> buttons = newArrayList();
for (PrnfsButton candidate : getSettings().getButtons()) {
UserKey userKey = userManager.getRemoteUserKey();
if (canUseButton(candidate, userManager.isAdmin(userKey), userManager.isSystemAdmin(userKey))) {
buttons.add(candidate);
}
}
return ok(gson.toJson(buttons), APPLICATION_JSON).build();
}

static boolean canUseButton(PrnfsButton candidate, boolean isAdmin, boolean isSystemAdmin) {
if (candidate.getVisibility().equals(EVERYONE)) {
return TRUE;
}
if (isSystemAdmin && systemAdminOk.contains(candidate.getVisibility())) {
return TRUE;
} else if (isAdmin && adminOk.contains(candidate.getVisibility())) {
return TRUE;
} else if (candidate.getVisibility().equals(EVERYONE)) {
return TRUE;
}
return FALSE;
}

@POST
@Produces(APPLICATION_JSON)
public Response post(@Context HttpServletRequest request, @QueryParam("repositoryId") Integer repositoryId,
@QueryParam("pullRequestId") Long pullRequestId, @QueryParam("formIdentifier") final String formIdentifier)
throws Exception {
if (userManager.getRemoteUser(request) == null) {
return status(UNAUTHORIZED).build();
}

final PullRequest pullRequest = pullRequestService.getById(repositoryId, pullRequestId);
final PrnfsSettings settings = getSettings();
for (PrnfsNotification prnfsNotification : settings.getNotifications()) {
PrnfsPullRequestAction pullRequestAction = PrnfsPullRequestAction.valueOf(BUTTON_TRIGGER);
StashUser stashUser = userService.getUserBySlug(userManager.getRemoteUser(request).getUsername());
Map<PrnfsVariable, Supplier<String>> variables = new HashMap<PrnfsRenderer.PrnfsVariable, Supplier<String>>();
variables.put(BUTTON_TRIGGER_TITLE, new Supplier<String>() {
@Override
public String get() {
return find(settings.getButtons(), new Predicate<PrnfsButton>() {
@Override
public boolean apply(PrnfsButton input) {
return input.getFormIdentifier().equals(formIdentifier);
}
}).getTitle();
}
});
prnfsPullRequestEventListener.notify(prnfsNotification, pullRequestAction, pullRequest, stashUser, variables);
}
return status(OK).build();
}

private PrnfsSettings getSettings() throws Exception {
final PrnfsSettings settings = securityService.withPermission(ADMIN, "Getting config").call(
new Operation<PrnfsSettings, Exception>() {
@Override
public PrnfsSettings perform() throws Exception {
return getPrnfsSettings(pluginSettingsFactory.createGlobalSettings());
}
});
return settings;
}
}
10 changes: 9 additions & 1 deletion src/main/java/se/bjurr/prnfs/admin/AdminFormValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ public final class AdminFormValues extends ArrayList<Map<String, String>> {

public static final String DEFAULT_NAME = "Unnamed trigger";

public enum FORM_TYPE {
BUTTON_CONFIG_FORM, TRIGGER_CONFIG_FORM
};

public enum BUTTON_VISIBILITY {
NONE, SYSTEM_ADMIN, ADMIN, EVERYONE
};

public enum FIELDS {
user, password, events, FORM_IDENTIFIER, url, filter_string, filter_regexp, method, post_content, proxy_user, proxy_password, proxy_server, proxy_port, header_name, header_value, name
user, password, events, FORM_IDENTIFIER, FORM_TYPE, url, filter_string, filter_regexp, method, post_content, proxy_user, proxy_password, proxy_server, proxy_port, header_name, header_value, name, button_title, button_visibility
}
}
22 changes: 17 additions & 5 deletions src/main/java/se/bjurr/prnfs/admin/ConfigResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
import static se.bjurr.prnfs.settings.PrnfsNotification.isOfType;
import static se.bjurr.prnfs.settings.SettingsStorage.checkFieldsRecognized;
import static se.bjurr.prnfs.settings.SettingsStorage.deleteSettings;
import static se.bjurr.prnfs.settings.SettingsStorage.getPrnfsButton;
import static se.bjurr.prnfs.settings.SettingsStorage.getPrnfsNotification;
import static se.bjurr.prnfs.settings.SettingsStorage.getSettingsAsFormValues;
import static se.bjurr.prnfs.settings.SettingsStorage.injectFormIdentifierIfNotSet;
import static se.bjurr.prnfs.settings.SettingsStorage.storeSettings;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -50,7 +54,7 @@ public ConfigResource(UserManager userManager, PluginSettingsFactory pluginSetti
@DELETE
@Path("{id}")
public Response delete(@PathParam("id") final String id, @Context HttpServletRequest request) {
if (!isAdminLoggedIn(request)) {
if (!isSystemAdminLoggedIn(request)) {
return status(UNAUTHORIZED).build();
}

Expand All @@ -70,7 +74,7 @@ public Object doInTransaction() {
@GET
@Produces(APPLICATION_JSON)
public Response get(@Context HttpServletRequest request) {
if (!isAdminLoggedIn(request)) {
if (!isSystemAdminLoggedIn(request)) {
return status(UNAUTHORIZED).build();
}

Expand All @@ -94,7 +98,7 @@ public UserManager getUserManager() {
return userManager;
}

private boolean isAdminLoggedIn(HttpServletRequest request) {
private boolean isSystemAdminLoggedIn(HttpServletRequest request) {
final UserProfile user = userManager.getRemoteUser(request);
if (user == null) {
return false;
Expand All @@ -109,15 +113,23 @@ private boolean isAdminLoggedIn(HttpServletRequest request) {
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public Response post(final AdminFormValues config, @Context HttpServletRequest request) {
if (!isAdminLoggedIn(request)) {
if (!isSystemAdminLoggedIn(request)) {
return status(UNAUTHORIZED).build();
}

/**
* Validate
*/
try {
getPrnfsNotification(config);
injectFormIdentifierIfNotSet(config);
checkFieldsRecognized(config);
if (isOfType(config, AdminFormValues.FORM_TYPE.TRIGGER_CONFIG_FORM)) {
// Assuming TRIGGER_CONFIG_FORM here if field not available, to be backwards
// compatible
getPrnfsNotification(config);
} else {
getPrnfsButton(config);
}
} catch (final ValidationException e) {
return status(BAD_REQUEST).entity(new AdminFormError(e.getField(), e.getError())).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class PrnfsPullRequestAction {

public static final String RESCOPED_FROM = "RESCOPED_FROM";

public static final String BUTTON_TRIGGER = "BUTTON_TRIGGER";

private static final Map<String, PrnfsPullRequestAction> values = new ImmutableMap.Builder<String, PrnfsPullRequestAction>()
.put(APPROVED.name(), new PrnfsPullRequestAction(APPROVED.name())) //
.put(COMMENTED.name(), new PrnfsPullRequestAction(COMMENTED.name())) //
Expand All @@ -38,6 +40,7 @@ public class PrnfsPullRequestAction {
.put(RESCOPED_TO, new PrnfsPullRequestAction(RESCOPED_TO)) //
.put(UNAPPROVED.name(), new PrnfsPullRequestAction(UNAPPROVED.name())) //
.put(UPDATED.name(), new PrnfsPullRequestAction(UPDATED.name())) //
.put(BUTTON_TRIGGER, new PrnfsPullRequestAction(BUTTON_TRIGGER)) //
.build();

private final String name;
Expand Down
Loading

0 comments on commit a3854c7

Please sign in to comment.