aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/it/analysis/FavoriteTest.java16
-rw-r--r--it/it-tests/src/test/java/it/user/FavoritesWsTest.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java67
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java191
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java48
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java1
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json24
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java1
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java210
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java39
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java2
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb89
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java17
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java1
-rw-r--r--sonar-ws/src/main/protobuf/ws-favorites.proto39
20 files changed, 571 insertions, 220 deletions
diff --git a/it/it-tests/src/test/java/it/analysis/FavoriteTest.java b/it/it-tests/src/test/java/it/analysis/FavoriteTest.java
index f066888a61a..c8602a377a6 100644
--- a/it/it-tests/src/test/java/it/analysis/FavoriteTest.java
+++ b/it/it-tests/src/test/java/it/analysis/FavoriteTest.java
@@ -28,9 +28,9 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
-import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.Favorites;
+import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.WsPermissions;
-import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.permission.AddProjectCreatorToTemplateWsRequest;
import org.sonarqube.ws.client.permission.RemoveProjectCreatorFromTemplateWsRequest;
@@ -71,8 +71,8 @@ public class FavoriteTest {
orchestrator.executeBuild(sampleProject);
- String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
- assertThat(response).contains(PROJECT_KEY);
+ Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
+ assertThat(response.getFavoritesList()).extracting(Favorite::getKey).contains(PROJECT_KEY);
}
@Test
@@ -81,8 +81,8 @@ public class FavoriteTest {
orchestrator.executeBuild(sampleProject);
- String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
- assertThat(response).doesNotContain(PROJECT_KEY);
+ Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
+ assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY);
}
@Test
@@ -94,8 +94,8 @@ public class FavoriteTest {
orchestrator.executeBuild(sampleProject);
- String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
- assertThat(response).doesNotContain(PROJECT_KEY);
+ Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
+ assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY);
}
private static SonarScanner createScannerWithUserCredentials() {
diff --git a/it/it-tests/src/test/java/it/user/FavoritesWsTest.java b/it/it-tests/src/test/java/it/user/FavoritesWsTest.java
index 62ec54251e8..357d1fa3bf2 100644
--- a/it/it-tests/src/test/java/it/user/FavoritesWsTest.java
+++ b/it/it-tests/src/test/java/it/user/FavoritesWsTest.java
@@ -26,9 +26,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
-import org.sonar.wsclient.Sonar;
-import org.sonar.wsclient.services.Favourite;
-import org.sonar.wsclient.services.FavouriteQuery;
+import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.client.WsClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,24 +51,22 @@ public class FavoritesWsTest {
@Test
public void favorites_web_service() {
- Sonar oldWsClient = orchestrator.getServer().getAdminWsClient();
-
// GET (nothing)
- List<Favourite> favourites = oldWsClient.findAll(new FavouriteQuery());
- assertThat(favourites).isEmpty();
+ List<Favorite> favorites = adminClient.favorites().search(null, null).getFavoritesList();
+ assertThat(favorites).isEmpty();
// POST (create favorites)
adminClient.favorites().add("sample");
adminClient.favorites().add("sample:src/main/xoo/sample/Sample.xoo");
// GET (created favorites)
- favourites = oldWsClient.findAll(new FavouriteQuery());
- assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo");
+ favorites = adminClient.favorites().search(null, null).getFavoritesList();
+ assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo");
// DELETE (a favorite)
adminClient.favorites().remove("sample");
- favourites = oldWsClient.findAll(new FavouriteQuery());
- assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo");
+ favorites = adminClient.favorites().search(null, null).getFavoritesList();
+ assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo");
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java
index 89bb917aef7..88c2180a190 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java
@@ -44,7 +44,6 @@ public class JwtCsrfVerifier {
private static final String API_URL = "/api";
private static final Set<String> RAILS_UPDATE_API_URLS = ImmutableSet.of(
"/api/events",
- "/api/favourites",
"/api/issues/add_comment",
"/api/issues/delete_comment",
"/api/issues/edit_comment",
diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java
new file mode 100644
index 00000000000..290e845abbe
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java
@@ -0,0 +1,67 @@
+/*
+ * 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.favorite;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+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 java.util.Collections.emptyList;
+import static org.sonar.core.util.stream.Collectors.toList;
+import static org.sonar.server.favorite.FavoriteUpdater.PROP_FAVORITE_KEY;
+
+public class FavoriteFinder {
+ private final DbClient dbClient;
+ private final UserSession userSession;
+
+ public FavoriteFinder(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ /**
+ * @return the list of favorite components of the authenticated user. Empty list if the user is not authenticated
+ */
+ public List<ComponentDto> list() {
+ if (!userSession.isLoggedIn()) {
+ return emptyList();
+ }
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ PropertyQuery dbQuery = PropertyQuery.builder()
+ .setKey(PROP_FAVORITE_KEY)
+ .setUserId(userSession.getUserId())
+ .build();
+ Set<Long> componentIds = dbClient.propertiesDao().selectByQuery(dbQuery, dbSession).stream().map(PropertyDto::getResourceId).collect(Collectors.toSet());
+
+ return dbClient.componentDao().selectByIds(dbSession, componentIds).stream()
+ .sorted(Comparator.comparing(ComponentDto::name))
+ .collect(toList());
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java
index 0a0da3ecedd..414aa027e69 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java
@@ -24,16 +24,19 @@ import org.sonar.core.platform.Module;
import org.sonar.server.favorite.ws.AddAction;
import org.sonar.server.favorite.ws.FavoritesWs;
import org.sonar.server.favorite.ws.RemoveAction;
+import org.sonar.server.favorite.ws.SearchAction;
public class FavoriteModule extends Module {
@Override
protected void configureModule() {
add(
+ FavoriteFinder.class,
FavoriteUpdater.class,
FavoritesWs.class,
AddAction.class,
- RemoveAction.class);
+ RemoveAction.class,
+ SearchAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
index 540e20fdb7f..067ca40bdb8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
@@ -30,7 +30,7 @@ import org.sonar.server.user.UserSession;
import static org.sonar.server.ws.WsUtils.checkRequest;
public class FavoriteUpdater {
- private static final String PROP_FAVORITE_KEY = "favourite";
+ static final String PROP_FAVORITE_KEY = "favourite";
private final DbClient dbClient;
private final UserSession userSession;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java
new file mode 100644
index 00000000000..0976cee4d87
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java
@@ -0,0 +1,191 @@
+/*
+ * 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.favorite.ws;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+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.Param;
+import org.sonar.api.utils.Paging;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.favorite.FavoriteFinder;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Favorites.Favorite;
+import org.sonarqube.ws.Favorites.SearchResponse;
+
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.core.util.stream.Collectors.toOneElement;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_SEARCH;
+
+public class SearchAction implements FavoritesWsAction {
+ private final FavoriteFinder favoriteFinder;
+ private final DbClient dbClient;
+ private final UserSession userSession;
+
+ public SearchAction(FavoriteFinder favoriteFinder, DbClient dbClient, UserSession userSession) {
+ this.favoriteFinder = favoriteFinder;
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction(ACTION_SEARCH)
+ .setDescription("Search for the authenticated user favorites.<br>" +
+ "Requires authentication.")
+ .setSince("6.3")
+ .setResponseExample(getClass().getResource("search-example.json"))
+ .setHandler(this);
+
+ action.addPagingParams(100, 500);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ SearchResponse wsResponse = Stream.of(request)
+ .map(search())
+ .map(new ResponseBuilder())
+ .collect(Collectors.toOneElement());
+ writeProtobuf(wsResponse, request, response);
+ }
+
+ private Function<Request, SearchResults> search() {
+ return request -> {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return Stream.of(request)
+ .peek(checkAuthentication(userSession))
+ .map(SearchResults.builder(dbSession))
+ .peek(addAuthorizedProjectUuids())
+ .peek(addFavorites())
+ .map(SearchResults.Builder::build)
+ .collect(Collectors.toOneElement());
+ }
+ };
+ }
+
+ private Consumer<SearchResults.Builder> addFavorites() {
+ return results -> results.allFavorites = favoriteFinder.list();
+ }
+
+ private Consumer<SearchResults.Builder> addAuthorizedProjectUuids() {
+ return results -> results.authorizedProjectUuids = ImmutableSet
+ .copyOf(dbClient.authorizationDao().selectAuthorizedRootProjectsUuids(results.dbSession, userSession.getUserId(), UserRole.USER));
+ }
+
+ private static Consumer<Request> checkAuthentication(UserSession userSession) {
+ return r -> userSession.checkLoggedIn();
+ }
+
+ private static class SearchResults {
+ private final List<ComponentDto> favorites;
+ private final Paging paging;
+
+ private SearchResults(Builder builder) {
+ Predicate<ComponentDto> authorizedProjects = c -> builder.authorizedProjectUuids.contains(c.projectUuid());
+ int total = (int) builder.allFavorites.stream().filter(authorizedProjects).count();
+ this.paging = Paging.forPageIndex(builder.page).withPageSize(builder.pageSize).andTotal(total);
+ this.favorites = builder.allFavorites.stream()
+ .filter(authorizedProjects)
+ .skip(paging.offset())
+ .limit(paging.pageSize())
+ .collect(Collectors.toList());
+ }
+
+ static Function<Request, Builder> builder(DbSession dbSession) {
+ return request -> new Builder(dbSession, request);
+ }
+
+ private static class Builder {
+ private final DbSession dbSession;
+ private final int page;
+ private final int pageSize;
+ private Set<String> authorizedProjectUuids;
+ private List<ComponentDto> allFavorites;
+
+ private Builder(DbSession dbSession, Request request) {
+ this.dbSession = dbSession;
+ this.page = request.mandatoryParamAsInt(Param.PAGE);
+ this.pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+ }
+
+ public SearchResults build() {
+ return new SearchResults(this);
+ }
+ }
+ }
+
+ private static class ResponseBuilder implements Function<SearchResults, SearchResponse> {
+ private final SearchResponse.Builder response;
+ private final Favorite.Builder favorite;
+
+ private ResponseBuilder() {
+ this.response = SearchResponse.newBuilder();
+ this.favorite = Favorite.newBuilder();
+ }
+
+ @Override
+ public SearchResponse apply(SearchResults searchResults) {
+ return Stream.of(searchResults)
+ .peek(addPaging())
+ .peek(addFavorites())
+ .map(results -> response.build())
+ .collect(toOneElement());
+ }
+
+ private Consumer<SearchResults> addPaging() {
+ return results -> response.setPaging(Common.Paging.newBuilder()
+ .setPageIndex(results.paging.pageIndex())
+ .setPageSize(results.paging.pageSize())
+ .setTotal(results.paging.total()));
+ }
+
+ private Consumer<SearchResults> addFavorites() {
+ return results -> results.favorites.stream()
+ .map(toWsFavorite())
+ .forEach(response::addFavorites);
+ }
+
+ private Function<ComponentDto, Favorite> toWsFavorite() {
+ return componentDto -> {
+ favorite
+ .clear()
+ .setKey(componentDto.key());
+ setNullable(componentDto.name(), favorite::setName);
+ setNullable(componentDto.qualifier(), favorite::setQualifier);
+ return favorite.build();
+ };
+ }
+
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java
deleted file mode 100644
index 543ee6fd39f..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.user.ws;
-
-import org.sonar.api.server.ws.RailsHandler;
-import org.sonar.api.server.ws.WebService;
-
-import static org.sonar.api.server.ws.RailsHandler.addFormatParam;
-
-public class FavouritesWs implements WebService {
-
- @Override
- public void define(Context context) {
- NewController controller = context.createController("api/favourites");
- controller.setDescription("Manage user favorites.");
- controller.setSince("2.6");
-
- defineIndexAction(controller);
-
- controller.done();
- }
-
- private void defineIndexAction(NewController controller) {
- NewAction action = controller.createAction("index")
- .setDescription("Documentation of this web service is available <a href=\"http://redirect.sonarsource.com/doc/old-web-service-api.html\">here</a>")
- .setResponseExample(getClass().getResource("favourites-index-example.xml"))
- .setSince("2.6")
- .setHandler(RailsHandler.INSTANCE);
- addFormatParam(action);
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java
index a568fb8c10a..18a35ad7741 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java
@@ -35,7 +35,6 @@ public class UsersWsModule extends Module {
SearchAction.class,
GroupsAction.class,
IdentityProvidersAction.class,
- FavouritesWs.class,
UserPropertiesWs.class,
UserJsonWriter.class);
}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json
new file mode 100644
index 00000000000..74d0909f625
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json
@@ -0,0 +1,24 @@
+{
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 3
+ },
+ "favorites": [
+ {
+ "key": "K2",
+ "name": "Apache HBase",
+ "qualifier": "TRK"
+ },
+ {
+ "key": "K3",
+ "name": "JDK9",
+ "qualifier": "TRK"
+ },
+ {
+ "key": "K1",
+ "name": "Samba",
+ "qualifier": "TRK"
+ }
+ ]
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml
deleted file mode 100644
index a5390359be2..00000000000
--- a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<favourites>
- <favourite>
- <id>2865</id>
- <key>org.sonarsource.sonarqube:sonarqube</key>
- <name>SonarQube</name>
- <lname>SonarQube</lname>
- <scope>PRJ</scope>
- <qualifier>TRK</qualifier>
- </favourite>
- <favourite>
- <id>34830</id>
- <key>DEV:george.orwell@1984.com</key>
- <name>George Orwell</name>
- <lname>George Orwell</lname>
- <scope>PRJ</scope>
- <qualifier>DEV</qualifier>
- </favourite>
-</favourites>
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
index 61babbeab1c..942ee39420a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
@@ -149,7 +149,6 @@ public class JwtCsrfVerifierTest {
@Test
public void ignore_rails_ws_requests() throws Exception {
executeVerifyStateDoesNotFailOnRequest("/api/events", "POST");
- executeVerifyStateDoesNotFailOnRequest("/api/favourites", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/add_comment?key=ABCD", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/delete_comment?key=ABCD", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/edit_comment?key=ABCD", "POST");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java
index 1affa2ca117..64086da9ce7 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java
@@ -30,6 +30,6 @@ public class FavoriteModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new FavoriteModule().configure(container);
- assertThat(container.size()).isEqualTo(4 + 2);
+ assertThat(container.size()).isEqualTo(6 + 2);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java
new file mode 100644
index 00000000000..817f726f97b
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.favorite.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.web.UserRole;
+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.permission.UserPermissionDto;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.favorite.FavoriteFinder;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Common.Paging;
+import org.sonarqube.ws.Favorites.Favorite;
+import org.sonarqube.ws.Favorites.SearchResponse;
+import org.sonarqube.ws.MediaTypes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.resources.Qualifiers.FILE;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.client.WsRequest.Method.POST;
+
+public class SearchActionTest {
+ private static final int USER_ID = 123;
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(USER_ID);
+ @Rule
+ public DbTester db = DbTester.create();
+ private DbClient dbClient = db.getDbClient();
+ private DbSession dbSession = db.getSession();
+
+ private FavoriteFinder favoriteFinder = new FavoriteFinder(dbClient, userSession);
+
+ private WsActionTester ws = new WsActionTester(new SearchAction(favoriteFinder, dbClient, userSession));
+
+ @Test
+ public void return_favorites() {
+ ComponentDto project = newProjectDto("P1").setKey("K1").setName("N1");
+ addComponent(project);
+ addComponent(newFileDto(project).setKey("K11").setName("N11"));
+ addComponent(newProjectDto("P2").setKey("K2").setName("N2"));
+
+ SearchResponse result = call();
+
+ assertThat(result.getPaging())
+ .extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
+ .containsExactly(1, 100, 3);
+ assertThat(result.getFavoritesList())
+ .extracting(Favorite::getKey, Favorite::getName, Favorite::getQualifier)
+ .containsOnly(
+ tuple("K1", "N1", PROJECT),
+ tuple("K11", "N11", FILE),
+ tuple("K2", "N2", PROJECT));
+ }
+
+ @Test
+ public void empty_list() {
+ SearchResponse result = call();
+
+ assertThat(result.getFavoritesCount()).isEqualTo(0);
+ assertThat(result.getFavoritesList()).isEmpty();
+ }
+
+ @Test
+ public void filter_authorized_components() {
+ addComponent(newProjectDto().setKey("K1"));
+ ComponentDto unauthorizedProject = db.components().insertComponent(newProjectDto());
+ db.favorites().add(unauthorizedProject, USER_ID);
+
+ SearchResponse result = call();
+
+ assertThat(result.getFavoritesCount()).isEqualTo(1);
+ assertThat(result.getFavorites(0).getKey()).isEqualTo("K1");
+ }
+
+ @Test
+ public void paginate_results() {
+ IntStream.rangeClosed(1, 9)
+ .forEach(i -> addComponent(newProjectDto().setKey("K" + i).setName("N" + i)));
+ ComponentDto unauthorizedProject = db.components().insertComponent(newProjectDto());
+ db.favorites().add(unauthorizedProject, USER_ID);
+
+ SearchResponse result = call(2, 3);
+
+ assertThat(result.getFavoritesCount()).isEqualTo(3);
+ assertThat(result.getFavoritesList())
+ .extracting(Favorite::getKey)
+ .containsExactly("K4", "K5", "K6");
+
+ }
+
+ @Test
+ public void return_only_users_favorite() {
+ addComponent(newProjectDto().setKey("K1"));
+ ComponentDto otherUserFavorite = newProjectDto().setKey("K42");
+ db.components().insertComponent(otherUserFavorite);
+ db.favorites().add(otherUserFavorite, 42L);
+ dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto("O1", UserRole.USER, 42L, otherUserFavorite.getId()));
+ db.commit();
+
+ SearchResponse result = call();
+
+ assertThat(result.getFavoritesList()).extracting(Favorite::getKey).containsExactly("K1");
+ }
+
+ @Test
+ public void favorites_ordered_by_name() {
+ addComponent(newProjectDto().setName("N2"));
+ addComponent(newProjectDto().setName("N3"));
+ addComponent(newProjectDto().setName("N1"));
+
+ SearchResponse result = call();
+
+ assertThat(result.getFavoritesList()).extracting(Favorite::getName)
+ .containsExactly("N1", "N2", "N3");
+ }
+
+ @Test
+ public void json_example() {
+ addComponent(newProjectDto().setKey("K1").setName("Samba"));
+ addComponent(newProjectDto().setKey("K2").setName("Apache HBase"));
+ addComponent(newProjectDto().setKey("K3").setName("JDK9"));
+
+ String result = ws.newRequest().execute().getInput();
+
+ assertJson(result).isSimilarTo(getClass().getResource("search-example.json"));
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("search");
+ assertThat(definition.responseExampleAsString()).isNotEmpty();
+ }
+
+ @Test
+ public void fail_if_not_authenticated() {
+ userSession.anonymous();
+
+ expectedException.expect(UnauthorizedException.class);
+
+ call();
+ }
+
+ private void addComponent(ComponentDto component) {
+ db.components().insertComponent(component);
+ db.favorites().add(component, USER_ID);
+ dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto("O1", UserRole.USER, USER_ID, component.getId()));
+ db.commit();
+ }
+
+ private SearchResponse call(@Nullable Integer page, @Nullable Integer pageSize) {
+ TestRequest request = ws.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF)
+ .setMethod(POST.name());
+ setNullable(page, p -> request.setParam(Param.PAGE, p.toString()));
+ setNullable(pageSize, ps -> request.setParam(Param.PAGE_SIZE, ps.toString()));
+
+ InputStream response = request.execute().getInputStream();
+
+ try {
+ return SearchResponse.parseFrom(response);
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private SearchResponse call() {
+ return call(null, null);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java
deleted file mode 100644
index b3b82202ea6..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.user.ws;
-
-import org.junit.Test;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.server.ws.WsTester;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class FavouritesWsTest {
-
- WsTester tester = new WsTester(new FavouritesWs());
-
- @Test
- public void define_ws() {
- WebService.Controller controller = tester.controller("api/favourites");
- assertThat(controller).isNotNull();
- assertThat(controller.description()).isNotEmpty();
- assertThat(controller.actions()).hasSize(1);
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java
index acf5f1c6fdb..845e0ef9137 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java
@@ -30,6 +30,6 @@ public class UsersWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new UsersWsModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 12);
+ assertThat(container.size()).isEqualTo(2 + 11);
}
}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb
deleted file mode 100644
index c1c3984b027..00000000000
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube 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.
-#
-# SonarQube 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.
-#
-
-require 'json'
-
-class Api::FavouritesController < Api::ApiController
-
- before_filter :login_required
-
- #
- # GET /api/favourites
- # curl http://localhost:9000/api/favourites -v -u admin:admin
- #
- def index
- respond_to do |format|
- format.json { render :json => jsonp(favourites_to_json(current_user.favourites)) }
- format.xml { render :xml => favourites_to_xml(current_user.favourites) }
- format.text { render :text => text_not_supported }
- end
- end
-
- def favourites_to_json(favourites=[])
- json=[]
- favourites.each do |f|
- json<<favourite_to_json(f)
- end
- json
- end
-
- def favourite_to_json(favourite)
- hash={}
- hash['id']=favourite.id
- hash['key']=favourite.key
- hash['name']=favourite.name
- hash['scope']=favourite.scope
- hash['branch']=favourite.branch if favourite.branch
- hash['lname']=favourite.long_name if favourite.long_name
- hash['lang']=favourite.language if favourite.language
- hash['qualifier']=favourite.qualifier
- hash
- end
-
- def favourites_to_xml(favourites, xml=Builder::XmlMarkup.new(:indent => 0))
- xml.favourites do
- favourites.each do |f|
- xml.favourite do
- xml.id(f.id)
- xml.key(f.key)
- xml.name(f.name)
- xml.lname(f.long_name) if f.long_name
- xml.branch(f.branch) if f.branch
- xml.scope(f.scope)
- xml.qualifier(f.qualifier)
- xml.lang(f.language) if f.language
- end
- end
- end
- end
-
- def favourite_to_xml(favourite, xml=Builder::XmlMarkup.new(:indent => 0))
- xml.favourite do
- xml.id(f.id)
- xml.key(f.key)
- xml.name(f.name)
- xml.lname(f.long_name) if f.long_name
- xml.branch(f.branch) if f.branch
- xml.scope(f.scope)
- xml.qualifier(f.qualifier)
- xml.lang(f.language) if f.language
- end
- end
-end
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java
index 91eede2140b..c24e8c3f7a1 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java
@@ -20,12 +20,17 @@
package org.sonarqube.ws.client.favorite;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonarqube.ws.Favorites.SearchResponse;
import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_ADD;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_REMOVE;
+import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.CONTROLLER_FAVORITES;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.PARAM_COMPONENT;
@@ -45,4 +50,16 @@ public class FavoritesService extends BaseService {
call(post);
}
+
+ public SearchResponse search(@Nullable Integer page, @Nullable Integer pageSize) {
+ GetRequest get = new GetRequest(path(ACTION_SEARCH));
+ if (page != null) {
+ get.setParam(Param.PAGE, page);
+ }
+ if (pageSize != null) {
+ get.setParam(Param.PAGE_SIZE, pageSize);
+ }
+
+ return call(get, SearchResponse.parser());
+ }
}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java
index 3c39c735ede..bca2767e415 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java
@@ -25,6 +25,7 @@ public class FavoritesWsParameters {
public static final String ACTION_ADD = "add";
public static final String ACTION_REMOVE = "remove";
+ public static final String ACTION_SEARCH = "search";
public static final String PARAM_COMPONENT = "component";
diff --git a/sonar-ws/src/main/protobuf/ws-favorites.proto b/sonar-ws/src/main/protobuf/ws-favorites.proto
new file mode 100644
index 00000000000..64a75848238
--- /dev/null
+++ b/sonar-ws/src/main/protobuf/ws-favorites.proto
@@ -0,0 +1,39 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 SonarSource
+// mailto:contact AT sonarsource DOT com
+//
+// SonarQube 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.
+//
+// SonarQube 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.
+
+syntax = "proto2";
+
+package sonarqube.ws.favorite;
+
+import "ws-commons.proto";
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "Favorites";
+option optimize_for = SPEED;
+
+// WS api/favorites/search
+message SearchResponse {
+ optional sonarqube.ws.commons.Paging paging = 1;
+ repeated Favorite favorites = 2;
+}
+
+message Favorite {
+ optional string key = 1;
+ optional string name = 2;
+ optional string qualifier = 3;
+}