Browse Source

SONAR-8555 SONAR-8556 Create WS api/notifications/add and remove

tags/6.3-RC1
Teryk Bellahsene 7 years ago
parent
commit
3c0c955eb5
22 changed files with 1363 additions and 54 deletions
  1. 2
    12
      server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
  2. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  3. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/notification/NotificationCenter.java
  4. 48
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/NotificationModule.java
  5. 102
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/NotificationUpdater.java
  6. 162
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/ws/AddAction.java
  7. 46
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/ws/NotificationsWs.java
  8. 27
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/ws/NotificationsWsAction.java
  9. 162
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/ws/RemoveAction.java
  10. 25
    0
      server/sonar-server/src/main/java/org/sonar/server/notification/ws/package-info.java
  11. 2
    16
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  12. 15
    21
      server/sonar-server/src/test/java/org/sonar/server/notification/NotificationCenterTest.java
  13. 35
    0
      server/sonar-server/src/test/java/org/sonar/server/notification/NotificationModuleTest.java
  14. 223
    0
      server/sonar-server/src/test/java/org/sonar/server/notification/ws/AddActionTest.java
  15. 56
    0
      server/sonar-server/src/test/java/org/sonar/server/notification/ws/NotificationsWsTest.java
  16. 234
    0
      server/sonar-server/src/test/java/org/sonar/server/notification/ws/RemoveActionTest.java
  17. 3
    1
      sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java
  18. 7
    0
      sonar-db/src/test/java/org/sonar/db/DbTester.java
  19. 65
    0
      sonar-db/src/test/java/org/sonar/db/notification/NotificationDbTester.java
  20. 86
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/notification/AddRequest.java
  21. 34
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/notification/NotificationsWsParameters.java
  22. 25
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/notification/package-info.java

+ 2
- 12
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java View File

@@ -24,7 +24,6 @@ import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.api.SonarQubeSide;
import org.sonar.api.SonarQubeVersion;
import org.sonar.api.config.EmailSettings;
import org.sonar.api.internal.ApiVersion;
import org.sonar.api.internal.SonarRuntimeImpl;
import org.sonar.api.profiles.AnnotationProfileParser;
@@ -90,11 +89,7 @@ import org.sonar.server.issue.workflow.FunctionExecutor;
import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.server.metric.CoreCustomMetrics;
import org.sonar.server.metric.DefaultMetricFinder;
import org.sonar.server.notification.DefaultNotificationManager;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationService;
import org.sonar.server.notification.email.AlertsEmailTemplate;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.notification.NotificationModule;
import org.sonar.server.organization.DefaultOrganizationProviderImpl;
import org.sonar.server.permission.GroupPermissionChanger;
import org.sonar.server.permission.PermissionTemplateService;
@@ -354,7 +349,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
NewIssuesEmailTemplate.class,
MyNewIssuesEmailTemplate.class,
IssueChangesEmailTemplate.class,
AlertsEmailTemplate.class,
ChangesOnMyIssueNotificationDispatcher.class,
ChangesOnMyIssueNotificationDispatcher.newMetadata(),
NewIssuesNotificationDispatcher.class,
@@ -364,17 +358,13 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
DoNotFixNotificationDispatcher.class,
DoNotFixNotificationDispatcher.newMetadata(),
NewIssuesNotificationFactory.class, // used by SendIssueNotificationsStep
EmailNotificationChannel.class,

// technical debt
DebtModelPluginRepository.class,
DebtRulesXMLImporter.class,

// Notifications
EmailSettings.class,
NotificationService.class,
NotificationCenter.class,
DefaultNotificationManager.class,
NotificationModule.class,

// Tests
TestIndexer.class,

+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

@@ -88,7 +88,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
+ 77 // level 4
+ 83 // level 4
+ 4 // content of CeConfigurationModule
+ 3 // content of CeHttpModule
+ 5 // content of CeQueueModule

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationCenter.java View File

@@ -19,7 +19,7 @@
*/
package org.sonar.server.notification;

import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
@@ -69,7 +69,7 @@ public class NotificationCenter {
* If "propertyValue" is null, the verification is done on the existence of such a property (whatever the value).
*/
public List<String> getDispatcherKeysForProperty(String propertyKey, @Nullable String propertyValue) {
List<String> keys = Lists.newArrayList();
ImmutableList.Builder<String> keys = ImmutableList.builder();
for (NotificationDispatcherMetadata metadata : dispatchersMetadata) {
String dispatcherKey = metadata.getDispatcherKey();
String value = metadata.getProperty(propertyKey);
@@ -77,7 +77,7 @@ public class NotificationCenter {
keys.add(dispatcherKey);
}
}
return keys;
return keys.build();
}

}

+ 48
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationModule.java View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification;

import org.sonar.api.config.EmailSettings;
import org.sonar.core.platform.Module;
import org.sonar.server.email.ws.EmailsWsModule;
import org.sonar.server.notification.email.AlertsEmailTemplate;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.notification.ws.AddAction;
import org.sonar.server.notification.ws.NotificationsWs;

public class NotificationModule extends Module {
@Override
protected void configureModule() {
add(
EmailSettings.class,
NotificationService.class,
NotificationCenter.class,
NotificationUpdater.class,
DefaultNotificationManager.class,
EmailsWsModule.class,
NotificationDaemon.class,
AlertsEmailTemplate.class,
EmailNotificationChannel.class,
// WS
NotificationsWs.class,
AddAction.class);
}
}

+ 102
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationUpdater.java View File

@@ -0,0 +1,102 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification;

import java.util.List;
import javax.annotation.Nullable;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
import org.sonar.server.user.UserSession;

import static com.google.common.base.Preconditions.checkArgument;

public class NotificationUpdater {
private static final String PROP_NOTIFICATION_PREFIX = "notification";
private static final String PROP_NOTIFICATION_VALUE = "true";

private final UserSession userSession;
private final DbClient dbClient;

public NotificationUpdater(UserSession userSession, DbClient dbClient) {
this.userSession = userSession;
this.dbClient = dbClient;
}

/**
* Add a notification to the current authenticated user.
* Does nothing if the user is not authenticated.
*/
public void add(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) {
if (!userSession.isLoggedIn()) {
return;
}

String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel);
Long projectId = project == null ? null : project.getId();

List<PropertyDto> existingNotification = dbClient.propertiesDao().selectByQuery(
PropertyQuery.builder()
.setKey(key)
.setComponentId(projectId)
.setUserId(userSession.getUserId())
.build(),
dbSession);
checkArgument(existingNotification.isEmpty()
|| !PROP_NOTIFICATION_VALUE.equals(existingNotification.get(0).getValue()), "Notification already added");

dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
.setKey(key)
.setUserId(Long.valueOf(userSession.getUserId()))
.setValue(PROP_NOTIFICATION_VALUE)
.setResourceId(projectId));
}

/**
* Remove a notification to the current authenticated user.
* Does nothing if the user is not authenticated.
*/
public void remove(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) {
if (!userSession.isLoggedIn()) {
return;
}

String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel);
Long projectId = project == null ? null : project.getId();

List<PropertyDto> existingNotification = dbClient.propertiesDao().selectByQuery(
PropertyQuery.builder()
.setKey(key)
.setComponentId(projectId)
.setUserId(userSession.getUserId())
.build(),
dbSession);
checkArgument(!existingNotification.isEmpty() && PROP_NOTIFICATION_VALUE.equals(existingNotification.get(0).getValue()), "Notification doesn't exist");

dbClient.propertiesDao().delete(dbSession, new PropertyDto()
.setKey(key)
.setUserId(Long.valueOf(userSession.getUserId()))
.setValue(PROP_NOTIFICATION_VALUE)
.setResourceId(projectId));
}
}

+ 162
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/ws/AddAction.java View File

@@ -0,0 +1,162 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationUpdater;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.client.notification.AddRequest;

import static java.util.Optional.empty;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_ADD;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;

public class AddAction implements NotificationsWsAction {
private final NotificationCenter notificationCenter;
private final NotificationUpdater notificationUpdater;
private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;
private final List<String> globalDispatchers;
private final List<String> projectDispatchers;

public AddAction(NotificationCenter notificationCenter, NotificationUpdater notificationUpdater, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
this.notificationCenter = notificationCenter;
this.notificationUpdater = notificationUpdater;
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
this.globalDispatchers = notificationCenter.getDispatcherKeysForProperty(GLOBAL_NOTIFICATION, "true");
this.projectDispatchers = notificationCenter.getDispatcherKeysForProperty(PER_PROJECT_NOTIFICATION, "true");
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_ADD)
.setDescription("Add a notification for the authenticated user.<br>" +
"Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.")
.setSince("6.3")
.setPost(true)
.setHandler(this);

action.createParam(PARAM_PROJECT)
.setDescription("Project key")
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);

List<NotificationChannel> channels = notificationCenter.getChannels();
action.createParam(PARAM_CHANNEL)
.setDescription("Channel through which the notification is sent. For example, notifications can be sent by email.")
.setPossibleValues(channels)
.setDefaultValue(EmailNotificationChannel.class.getSimpleName());

action.createParam(PARAM_NOTIFICATION)
.setDescription("Notification. Possible values are for:" +
"<ul>" +
" <li>Overall notifications: %s</li>" +
" <li>Per project notifications: %s</li>" +
"</ul>",
globalDispatchers.stream().sorted().collect(Collectors.joining(", ")),
projectDispatchers.stream().sorted().collect(Collectors.joining(", ")))
.setRequired(true)
.setExampleValue(MyNewIssuesNotificationDispatcher.KEY);
}

@Override
public void handle(Request request, Response response) throws Exception {
Stream.of(request)
.map(toWsRequest())
.peek(checkPermissions())
.forEach(add());

response.noContent();
}

private Consumer<AddRequest> add() {
return request -> {
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<ComponentDto> project = searchProject(dbSession, request);
notificationUpdater.add(dbSession, request.getChannel(), request.getNotification(), project.orElse(null));
dbSession.commit();
}
};
}

private Optional<ComponentDto> searchProject(DbSession dbSession, AddRequest request) {
Optional<ComponentDto> project = request.getProject() == null ? empty() : Optional.of(componentFinder.getByKey(dbSession, request.getProject()));
project.ifPresent(p -> checkRequest(Qualifiers.PROJECT.equals(p.qualifier()) && Scopes.PROJECT.equals(p.scope()),
"Component '%s' must be a project", request.getProject()));
return project;
}

private Consumer<AddRequest> checkPermissions() {
return request -> userSession.checkLoggedIn();
}

private Function<Request, AddRequest> toWsRequest() {
return request -> {
AddRequest.Builder requestBuilder = AddRequest.builder()
.setNotification(request.mandatoryParam(PARAM_NOTIFICATION))
.setChannel(request.mandatoryParam(PARAM_CHANNEL));
String project = request.param(PARAM_PROJECT);
setNullable(project, requestBuilder::setProject);
AddRequest wsRequest = requestBuilder.build();

if (wsRequest.getProject() == null) {
checkRequest(globalDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s",
PARAM_NOTIFICATION,
wsRequest.getNotification(),
globalDispatchers);
} else {
checkRequest(projectDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s",
PARAM_NOTIFICATION,
wsRequest.getNotification(),
projectDispatchers);
}

return wsRequest;
};
}
}

+ 46
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/ws/NotificationsWs.java View File

@@ -0,0 +1,46 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import org.sonar.api.server.ws.WebService;

import static org.sonarqube.ws.client.notification.NotificationsWsParameters.CONTROLLER;

public class NotificationsWs implements WebService {
private final NotificationsWsAction[] actions;

public NotificationsWs(NotificationsWsAction[] actions) {
this.actions = actions;
}

@Override
public void define(Context context) {
NewController controller = context.createController(CONTROLLER)
.setDescription("Manage notifications of the authenticated user")
.setSince("6.3");

for (NotificationsWsAction action : actions) {
action.define(controller);
}

controller.done();
}
}

+ 27
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/ws/NotificationsWsAction.java View File

@@ -0,0 +1,27 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import org.sonar.server.ws.WsAction;

public interface NotificationsWsAction extends WsAction {
// marker interface
}

+ 162
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/ws/RemoveAction.java View File

@@ -0,0 +1,162 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationUpdater;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.client.notification.AddRequest;

import static java.util.Optional.empty;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_ADD;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;

public class RemoveAction implements NotificationsWsAction {
private final NotificationCenter notificationCenter;
private final NotificationUpdater notificationUpdater;
private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;
private final List<String> globalDispatchers;
private final List<String> projectDispatchers;

public RemoveAction(NotificationCenter notificationCenter, NotificationUpdater notificationUpdater, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
this.notificationCenter = notificationCenter;
this.notificationUpdater = notificationUpdater;
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
this.globalDispatchers = notificationCenter.getDispatcherKeysForProperty(GLOBAL_NOTIFICATION, "true");
this.projectDispatchers = notificationCenter.getDispatcherKeysForProperty(PER_PROJECT_NOTIFICATION, "true");
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_ADD)
.setDescription("Remove a notification for the authenticated user.<br>" +
"Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.")
.setSince("6.3")
.setPost(true)
.setHandler(this);

action.createParam(PARAM_PROJECT)
.setDescription("Project key")
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);

List<NotificationChannel> channels = notificationCenter.getChannels();
action.createParam(PARAM_CHANNEL)
.setDescription("Channel through which the notification is sent. For example, notifications can be sent by email.")
.setPossibleValues(channels)
.setDefaultValue(EmailNotificationChannel.class.getSimpleName());

action.createParam(PARAM_NOTIFICATION)
.setDescription("Notification. Possible values are for:" +
"<ul>" +
" <li>Overall notifications: %s</li>" +
" <li>Per project notifications: %s</li>" +
"</ul>",
globalDispatchers.stream().sorted().collect(Collectors.joining(", ")),
projectDispatchers.stream().sorted().collect(Collectors.joining(", ")))
.setRequired(true)
.setExampleValue(MyNewIssuesNotificationDispatcher.KEY);
}

@Override
public void handle(Request request, Response response) throws Exception {
Stream.of(request)
.map(toWsRequest())
.peek(checkPermissions())
.forEach(remove());

response.noContent();
}

private Consumer<AddRequest> remove() {
return request -> {
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<ComponentDto> project = searchProject(dbSession, request);
notificationUpdater.remove(dbSession, request.getChannel(), request.getNotification(), project.orElse(null));
dbSession.commit();
}
};
}

private Optional<ComponentDto> searchProject(DbSession dbSession, AddRequest request) {
Optional<ComponentDto> project = request.getProject() == null ? empty() : Optional.of(componentFinder.getByKey(dbSession, request.getProject()));
project.ifPresent(p -> checkRequest(Qualifiers.PROJECT.equals(p.qualifier()) && Scopes.PROJECT.equals(p.scope()),
"Component '%s' must be a project", request.getProject()));
return project;
}

private Consumer<AddRequest> checkPermissions() {
return request -> userSession.checkLoggedIn();
}

private Function<Request, AddRequest> toWsRequest() {
return request -> {
AddRequest.Builder requestBuilder = AddRequest.builder()
.setNotification(request.mandatoryParam(PARAM_NOTIFICATION))
.setChannel(request.mandatoryParam(PARAM_CHANNEL));
String project = request.param(PARAM_PROJECT);
setNullable(project, requestBuilder::setProject);
AddRequest wsRequest = requestBuilder.build();

if (wsRequest.getProject() == null) {
checkRequest(globalDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s",
PARAM_NOTIFICATION,
wsRequest.getNotification(),
globalDispatchers);
} else {
checkRequest(projectDispatchers.contains(wsRequest.getNotification()), "Value of parameter '%s' (%s) must be one of: %s",
PARAM_NOTIFICATION,
wsRequest.getNotification(),
projectDispatchers);
}

return wsRequest;
};
}
}

+ 25
- 0
server/sonar-server/src/main/java/org/sonar/server/notification/ws/package-info.java View File

@@ -0,0 +1,25 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

@ParametersAreNonnullByDefault
package org.sonar.server.notification.ws;

import javax.annotation.ParametersAreNonnullByDefault;


+ 2
- 16
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -20,7 +20,6 @@
package org.sonar.server.platform.platformlevel;

import java.util.List;
import org.sonar.api.config.EmailSettings;
import org.sonar.api.profiles.AnnotationProfileParser;
import org.sonar.api.profiles.XMLProfileParser;
import org.sonar.api.profiles.XMLProfileSerializer;
@@ -52,7 +51,6 @@ import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.duplication.ws.DuplicationsJsonWriter;
import org.sonar.server.duplication.ws.DuplicationsParser;
import org.sonar.server.duplication.ws.DuplicationsWs;
import org.sonar.server.email.ws.EmailsWsModule;
import org.sonar.server.es.IndexCreator;
import org.sonar.server.es.IndexDefinitions;
import org.sonar.server.event.NewAlerts;
@@ -85,12 +83,7 @@ import org.sonar.server.measure.ws.TimeMachineWs;
import org.sonar.server.metric.CoreCustomMetrics;
import org.sonar.server.metric.DefaultMetricFinder;
import org.sonar.server.metric.ws.MetricsWsModule;
import org.sonar.server.notification.DefaultNotificationManager;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationDaemon;
import org.sonar.server.notification.NotificationService;
import org.sonar.server.notification.email.AlertsEmailTemplate;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.notification.NotificationModule;
import org.sonar.server.organization.ws.OrganizationsWsModule;
import org.sonar.server.permission.GroupPermissionChanger;
import org.sonar.server.permission.PermissionTemplateService;
@@ -396,8 +389,6 @@ public class PlatformLevel4 extends PlatformLevel {
DoNotFixNotificationDispatcher.class,
DoNotFixNotificationDispatcher.newMetadata(),
NewIssuesNotificationFactory.class,
EmailNotificationChannel.class,
AlertsEmailTemplate.class,

// issues actions
AssignAction.class,
@@ -437,12 +428,7 @@ public class PlatformLevel4 extends PlatformLevel {
RubyTextService.class,

// Notifications
EmailSettings.class,
NotificationService.class,
NotificationCenter.class,
DefaultNotificationManager.class,
EmailsWsModule.class,
NotificationDaemon.class,
NotificationModule.class,

// Tests
TestsWs.class,

+ 15
- 21
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationCenterTest.java View File

@@ -21,31 +21,25 @@ package org.sonar.server.notification;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.sonar.api.notifications.NotificationChannel;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

public class NotificationCenterTest {

@Mock
private NotificationChannel emailChannel;
private NotificationChannel emailChannel = mock(NotificationChannel.class);
private NotificationChannel gtalkChannel = mock(NotificationChannel.class);

@Mock
private NotificationChannel gtalkChannel;

private NotificationCenter notificationCenter;
private NotificationCenter underTest;

@Before
public void init() {
MockitoAnnotations.initMocks(this);

NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true").setProperty("on-project", "true");
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create("Dispatcher2").setProperty("global", "true");
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create("Dispatcher3").setProperty("global", "FOO").setProperty("on-project", "BAR");

notificationCenter = new NotificationCenter(
underTest = new NotificationCenter(
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
new NotificationChannel[] {emailChannel, gtalkChannel}
);
@@ -53,30 +47,30 @@ public class NotificationCenterTest {

@Test
public void shouldReturnChannels() {
assertThat(notificationCenter.getChannels()).containsOnly(emailChannel, gtalkChannel);
assertThat(underTest.getChannels()).containsOnly(emailChannel, gtalkChannel);
}

@Test
public void shouldReturnDispatcherKeysForSpecificPropertyValue() {
assertThat(notificationCenter.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2");
assertThat(underTest.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2");
}

@Test
public void shouldReturnDispatcherKeysForExistenceOfProperty() {
assertThat(notificationCenter.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3");
assertThat(underTest.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3");
}

@Test
public void testDefaultConstructors() {
notificationCenter = new NotificationCenter(new NotificationChannel[] {emailChannel});
assertThat(notificationCenter.getChannels()).hasSize(1);
underTest = new NotificationCenter(new NotificationChannel[] {emailChannel});
assertThat(underTest.getChannels()).hasSize(1);

notificationCenter = new NotificationCenter();
assertThat(notificationCenter.getChannels()).hasSize(0);
underTest = new NotificationCenter();
assertThat(underTest.getChannels()).hasSize(0);

notificationCenter = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")});
assertThat(notificationCenter.getChannels()).hasSize(0);
assertThat(notificationCenter.getDispatcherKeysForProperty("global", null)).hasSize(1);
underTest = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")});
assertThat(underTest.getChannels()).hasSize(0);
assertThat(underTest.getDispatcherKeysForProperty("global", null)).hasSize(1);
}

}

+ 35
- 0
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationModuleTest.java View File

@@ -0,0 +1,35 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification;

import org.junit.Test;
import org.sonar.core.platform.ComponentContainer;

import static org.assertj.core.api.Assertions.assertThat;

public class NotificationModuleTest {
@Test
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new NotificationModule().configure(container);
assertThat(container.size()).isEqualTo(11 + 2);
}
}

+ 223
- 0
server/sonar-server/src/test/java/org/sonar/server/notification/ws/AddActionTest.java View File

@@ -0,0 +1,223 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationDispatcherMetadata;
import org.sonar.server.notification.NotificationUpdater;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.client.notification.AddRequest;

import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;

public class AddActionTest {
private static final String NOTIF_MY_NEW_ISSUES = "Dispatcher1";
private static final String NOTIF_NEW_ISSUES = "Dispatcher2";
private static final String NOTIF_NEW_QUALITY_GATE_STATUS = "Dispatcher3";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(123);
@Rule
public DbTester db = DbTester.create();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();

private NotificationChannel emailChannel = new FakeNotificationChannel("EmailChannel");
private NotificationChannel twitterChannel = new FakeNotificationChannel("TwitterChannel");
// default channel, based on class simple name
private NotificationChannel defaultChannel = new FakeNotificationChannel("EmailNotificationChannel");

private NotificationCenter notificationCenter;
private AddAction underTest;
private WsActionTester ws;

private AddRequest.Builder request = AddRequest.builder()
.setNotification(NOTIF_MY_NEW_ISSUES);

@Before
public void setUp() {
NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create(NOTIF_MY_NEW_ISSUES)
.setProperty(GLOBAL_NOTIFICATION, "true")
.setProperty(PER_PROJECT_NOTIFICATION, "true");
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create(NOTIF_NEW_ISSUES)
.setProperty(GLOBAL_NOTIFICATION, "true");
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create(NOTIF_NEW_QUALITY_GATE_STATUS)
.setProperty(GLOBAL_NOTIFICATION, "true")
.setProperty(PER_PROJECT_NOTIFICATION, "true");

notificationCenter = new NotificationCenter(
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel});
underTest = new AddAction(notificationCenter, new NotificationUpdater(userSession, dbClient), dbClient, new ComponentFinder(dbClient), userSession);
ws = new WsActionTester(underTest);
}

@Test
public void add_to_email_channel_by_default() {
call(request);

db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), null);
}

@Test
public void add_to_a_specific_channel() {
call(request.setNotification(NOTIF_NEW_QUALITY_GATE_STATUS).setChannel(twitterChannel.getKey()));

db.notifications().assertExists(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, userSession.getUserId(), null);
}

@Test
public void add_a_project_notification() {
ComponentDto project = db.components().insertProject();

call(request.setProject(project.getKey()));

db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), project);
}

@Test
public void http_no_content() {
TestResponse result = call(request);

assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
}

@Test
public void fail_when_notification_already_exists() {
call(request);

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Notification already added");

call(request);
}

@Test
public void fail_when_unknown_channel() {
expectedException.expect(IllegalArgumentException.class);

call(request.setChannel("Channel42"));
}

@Test
public void fail_when_unknown_global_dispatcher() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher2, Dispatcher3]");

call(request.setNotification("Dispatcher42"));
}

@Test
public void fail_when_unknown_project_dispatcher() {
ComponentDto project = db.components().insertProject();

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher3]");

call(request.setNotification("Dispatcher42").setProject(project.key()));
}

@Test
public void fail_when_no_dispatcher() {
expectedException.expect(IllegalArgumentException.class);

ws.newRequest().execute();
}

@Test
public void fail_when_project_is_unknown() {
expectedException.expect(NotFoundException.class);

call(request.setProject("Project-42"));
}

@Test
public void fail_when_component_is_not_a_project() {
db.components().insertViewAndSnapshot(newView().setKey("VIEW_1"));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Component 'VIEW_1' must be a project");

call(request.setProject("VIEW_1"));
}

@Test
public void fail_when_not_authenticated() {
userSession.anonymous();

expectedException.expect(UnauthorizedException.class);

call(request);
}

private TestResponse call(AddRequest.Builder wsRequestBuilder) {
AddRequest wsRequest = wsRequestBuilder.build();
TestRequest request = ws.newRequest();
request.setParam(PARAM_NOTIFICATION, wsRequest.getNotification());
setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel));
setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
return request.execute();
}

private static class FakeNotificationChannel extends NotificationChannel {
private final String key;

private FakeNotificationChannel(String key) {
this.key = key;
}

@Override
public String getKey() {
return this.key;
}

@Override
public void deliver(Notification notification, String username) {
// do nothing
}
}
}

+ 56
- 0
server/sonar-server/src/test/java/org/sonar/server/notification/ws/NotificationsWsTest.java View File

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import org.junit.Test;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Controller;
import org.sonar.server.ws.WsTester;

import static org.assertj.core.api.Assertions.assertThat;

public class NotificationsWsTest {
private NotificationsWsAction action = new FakeNotificationAction();
private NotificationsWsAction[] actions = {action};
private WsTester ws = new WsTester(new NotificationsWs(actions));

private Controller underTest = ws.controller("api/notifications");

@Test
public void definition() {
assertThat(underTest.path()).isEqualTo("api/notifications");
}

private static class FakeNotificationAction implements NotificationsWsAction {
@Override
public void define(WebService.NewController context) {
context.createAction("fake")
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
// do nothing
}
}
}

+ 234
- 0
server/sonar-server/src/test/java/org/sonar/server/notification/ws/RemoveActionTest.java View File

@@ -0,0 +1,234 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.notification.ws;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.notification.NotificationCenter;
import org.sonar.server.notification.NotificationDispatcherMetadata;
import org.sonar.server.notification.NotificationUpdater;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.client.notification.AddRequest;

import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_NOTIFICATION;
import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;

public class RemoveActionTest {
private static final String NOTIF_MY_NEW_ISSUES = "Dispatcher1";
private static final String NOTIF_NEW_ISSUES = "Dispatcher2";
private static final String NOTIF_NEW_QUALITY_GATE_STATUS = "Dispatcher3";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(123);
@Rule
public DbTester db = DbTester.create();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();

private NotificationChannel emailChannel = new FakeNotificationChannel("EmailChannel");
private NotificationChannel twitterChannel = new FakeNotificationChannel("TwitterChannel");
// default channel, based on class simple name
private NotificationChannel defaultChannel = new FakeNotificationChannel("EmailNotificationChannel");

private NotificationCenter notificationCenter;
private NotificationUpdater notificationUpdater;
private RemoveAction underTest;

private WsActionTester ws;
private AddRequest.Builder request = AddRequest.builder()
.setNotification(NOTIF_MY_NEW_ISSUES);

@Before
public void setUp() {
NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create(NOTIF_MY_NEW_ISSUES)
.setProperty(GLOBAL_NOTIFICATION, "true")
.setProperty(PER_PROJECT_NOTIFICATION, "true");
NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create(NOTIF_NEW_ISSUES)
.setProperty(GLOBAL_NOTIFICATION, "true");
NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create(NOTIF_NEW_QUALITY_GATE_STATUS)
.setProperty(GLOBAL_NOTIFICATION, "true")
.setProperty(PER_PROJECT_NOTIFICATION, "true");

notificationCenter = new NotificationCenter(
new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel});
notificationUpdater = new NotificationUpdater(userSession, dbClient);
underTest = new RemoveAction(notificationCenter, notificationUpdater, dbClient, new ComponentFinder(dbClient), userSession);
ws = new WsActionTester(underTest);
}

@Test
public void remove_to_email_channel_by_default() {
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
dbSession.commit();

call(request);

db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), null);
}

@Test
public void remove_from_a_specific_channel() {
notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, null);
dbSession.commit();

call(request.setNotification(NOTIF_NEW_QUALITY_GATE_STATUS).setChannel(twitterChannel.getKey()));

db.notifications().assertDoesNotExist(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, userSession.getUserId(), null);
}

@Test
public void remove_a_project_notification() {
ComponentDto project = db.components().insertProject();
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
dbSession.commit();

call(request.setProject(project.getKey()));

db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), project);
}

@Test
public void http_no_content() {
notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
dbSession.commit();

TestResponse result = call(request);

assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
}

@Test
public void fail_when_notification_does_not_exist() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Notification doesn't exist");

call(request);
}

@Test
public void fail_when_unknown_channel() {
expectedException.expect(IllegalArgumentException.class);

call(request.setChannel("Channel42"));
}

@Test
public void fail_when_unknown_global_dispatcher() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher2, Dispatcher3]");

call(request.setNotification("Dispatcher42"));
}

@Test
public void fail_when_unknown_project_dispatcher() {
ComponentDto project = db.components().insertProject();

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Value of parameter 'notification' (Dispatcher42) must be one of: [Dispatcher1, Dispatcher3]");

call(request.setNotification("Dispatcher42").setProject(project.key()));
}

@Test
public void fail_when_no_dispatcher() {
expectedException.expect(IllegalArgumentException.class);

ws.newRequest().execute();
}

@Test
public void fail_when_project_is_unknown() {
expectedException.expect(NotFoundException.class);

call(request.setProject("Project-42"));
}

@Test
public void fail_when_component_is_not_a_project() {
db.components().insertViewAndSnapshot(newView().setKey("VIEW_1"));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Component 'VIEW_1' must be a project");

call(request.setProject("VIEW_1"));
}

@Test
public void fail_when_not_authenticated() {
userSession.anonymous();

expectedException.expect(UnauthorizedException.class);

call(request);
}

private TestResponse call(AddRequest.Builder wsRequestBuilder) {
AddRequest wsRequest = wsRequestBuilder.build();
TestRequest request = ws.newRequest();
request.setParam(PARAM_NOTIFICATION, wsRequest.getNotification());
setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel));
setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
return request.execute();
}

private static class FakeNotificationChannel extends NotificationChannel {
private final String key;

private FakeNotificationChannel(String key) {
this.key = key;
}

@Override
public String getKey() {
return this.key;
}

@Override
public void deliver(Notification notification, String username) {
// do nothing
}
}
}

+ 3
- 1
sonar-db/src/main/java/org/sonar/db/property/PropertyQuery.java View File

@@ -19,6 +19,8 @@
*/
package org.sonar.db.property;

import javax.annotation.Nullable;

public class PropertyQuery {

private final String key;
@@ -57,7 +59,7 @@ public class PropertyQuery {
return this;
}

public Builder setComponentId(Long componentId) {
public Builder setComponentId(@Nullable Long componentId) {
this.componentId = componentId;
return this;
}

+ 7
- 0
sonar-db/src/test/java/org/sonar/db/DbTester.java View File

@@ -67,6 +67,7 @@ import org.sonar.db.component.ComponentDbTester;
import org.sonar.db.event.EventDbTester;
import org.sonar.db.favorite.FavoriteDbTester;
import org.sonar.db.issue.IssueDbTester;
import org.sonar.db.notification.NotificationDbTester;
import org.sonar.db.organization.OrganizationDbTester;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.organization.OrganizationTesting;
@@ -110,6 +111,7 @@ public class DbTester extends ExternalResource {
private final QualityGateDbTester qualityGateDbTester;
private final IssueDbTester issueDbTester;
private final RuleDbTester ruleDbTester;
private final NotificationDbTester notificationDbTester;
private final RootFlagAssertions rootFlagAssertions;

private DbTester(System2 system2, @Nullable String schemaPath) {
@@ -125,6 +127,7 @@ public class DbTester extends ExternalResource {
this.qualityGateDbTester = new QualityGateDbTester(this);
this.issueDbTester = new IssueDbTester(this);
this.ruleDbTester = new RuleDbTester(this);
this.notificationDbTester = new NotificationDbTester(this);
this.rootFlagAssertions = new RootFlagAssertions(this);
}

@@ -225,6 +228,10 @@ public class DbTester extends ExternalResource {
return ruleDbTester;
}

public NotificationDbTester notifications() {
return notificationDbTester;
}

@Override
protected void after() {
if (session != null) {

+ 65
- 0
sonar-db/src/test/java/org/sonar/db/notification/NotificationDbTester.java View File

@@ -0,0 +1,65 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.db.notification;

import java.util.List;
import javax.annotation.Nullable;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;

import static org.assertj.core.api.Assertions.assertThat;

public class NotificationDbTester {
private static final String PROP_NOTIFICATION_PREFIX = "notification";

private final DbTester db;
private final DbClient dbClient;
private final DbSession dbSession;

public NotificationDbTester(DbTester db) {
this.db = db;
this.dbClient = db.getDbClient();
this.dbSession = db.getSession();
}

public void assertExists(String channel, String dispatcher, long userId, @Nullable ComponentDto component) {
List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
.setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel))
.setComponentId(component == null ? null : component.getId())
.setUserId((int) userId)
.build(), dbSession);
assertThat(result).hasSize(1);
assertThat(result.get(0).getValue()).isEqualTo("true");
}

public void assertDoesNotExist(String channel, String dispatcher, long userId, @Nullable ComponentDto component) {
List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
.setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel))
.setComponentId(component == null ? null : component.getId())
.setUserId((int) userId)
.build(), dbSession);
assertThat(result).isEmpty();
}
}

+ 86
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/AddRequest.java View File

@@ -0,0 +1,86 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonarqube.ws.client.notification;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import static java.util.Objects.requireNonNull;

public class AddRequest {
private final String notification;
private final String channel;
private final String project;

private AddRequest(Builder builder) {
this.channel = builder.channel;
this.notification = builder.notification;
this.project = builder.project;
}

public String getNotification() {
return notification;
}

@CheckForNull
public String getChannel() {
return channel;
}

@CheckForNull
public String getProject() {
return project;
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
private String notification;
private String channel;
private String project;

private Builder() {
// enforce factory method
}

public Builder setNotification(String notification) {
this.notification = notification;
return this;
}

public Builder setChannel(@Nullable String channel) {
this.channel = channel;
return this;
}

public Builder setProject(@Nullable String project) {
this.project = project;
return this;
}

public AddRequest build() {
requireNonNull(notification, "Notification is required");
return new AddRequest(this);
}
}
}

+ 34
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/NotificationsWsParameters.java View File

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonarqube.ws.client.notification;

public class NotificationsWsParameters {
public static final String CONTROLLER = "api/notifications";
public static final String ACTION_ADD = "add";

public static final String PARAM_PROJECT = "project";
public static final String PARAM_CHANNEL = "channel";
public static final String PARAM_NOTIFICATION = "notification";

private NotificationsWsParameters() {
// prevent instantiation
}
}

+ 25
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/package-info.java View File

@@ -0,0 +1,25 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

@ParametersAreNonnullByDefault
package org.sonarqube.ws.client.notification;

import javax.annotation.ParametersAreNonnullByDefault;


Loading…
Cancel
Save