aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2016-10-10 16:21:55 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2016-10-12 16:45:33 +0200
commitb081169a1d0ed70c29678d28271baea8885494db (patch)
treedec26c9de431f31ff34294f0dc6a6779b7afc03f
parent55dfa5865f5de6ae44f37dcf28985e7501d16fe5 (diff)
downloadsonarqube-b081169a1d0ed70c29678d28271baea8885494db.tar.gz
sonarqube-b081169a1d0ed70c29678d28271baea8885494db.zip
SONAR-8221 Create WS api/components/search_projects paginate and sort
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java143
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndex.java55
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndexDefinition.java53
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectsEsModule.java32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/es/package-info.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java1
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/component/ws/search_projects-example.json24
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java189
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresDoc.java81
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresIndexTest.java79
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectsEsModuleTest.java35
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java80
-rw-r--r--sonar-ws/src/main/protobuf/ws-components.proto6
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java59
17 files changed, 865 insertions, 2 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
index 425d7917d01..78d82efee89 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
@@ -35,6 +35,7 @@ public class ComponentsWsModule extends Module {
ShowAction.class,
SearchViewComponentsAction.class,
UpdateKeyAction.class,
- BulkUpdateKeyAction.class);
+ BulkUpdateKeyAction.class,
+ SearchProjectsAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
new file mode 100644
index 00000000000..ef7ffb4a3a5
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
@@ -0,0 +1,143 @@
+/*
+ * 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.component.ws;
+
+import com.google.common.collect.Ordering;
+import java.util.List;
+import java.util.function.Function;
+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.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.project.es.ProjectMeasuresIndex;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.WsComponents.Component;
+import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
+import org.sonarqube.ws.client.component.SearchProjectsRequest;
+
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.component.SearchProjectsRequest.DEFAULT_PAGE_SIZE;
+import static org.sonarqube.ws.client.component.SearchProjectsRequest.MAX_PAGE_SIZE;
+
+public class SearchProjectsAction implements ComponentsWsAction {
+ private final DbClient dbClient;
+ private final ProjectMeasuresIndex index;
+
+ public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index) {
+ this.dbClient = dbClient;
+ this.index = index;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ context.createAction("search_projects")
+ .setSince("6.2")
+ .setDescription("Search for projects")
+ .addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
+ .setInternal(true)
+ .setResponseExample(getClass().getResource("search_projects-example.json"))
+ .setHandler(this);
+ }
+
+ @Override
+ public void handle(Request httpRequest, Response httpResponse) throws Exception {
+ SearchProjectsWsResponse response = doHandle(toRequest(httpRequest));
+
+ writeProtobuf(response, httpRequest, httpResponse);
+ }
+
+ private SearchProjectsWsResponse doHandle(SearchProjectsRequest request) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ SearchResults searchResults = searchProjects(dbSession, request);
+
+ return buildResponse(request, searchResults);
+ }
+ }
+
+ private SearchResults searchProjects(DbSession dbSession, SearchProjectsRequest request) {
+ SearchIdResult<String> searchResult = index.search(new SearchOptions().setPage(request.getPage(), request.getPageSize()));
+
+ Ordering<ComponentDto> ordering = Ordering.explicit(searchResult.getIds()).onResultOf(ComponentDto::uuid);
+ List<ComponentDto> projects = ordering.immutableSortedCopy(dbClient.componentDao().selectByUuids(dbSession, searchResult.getIds()));
+
+ return new SearchResults(projects, searchResult.getTotal());
+ }
+
+ private static SearchProjectsRequest toRequest(Request httpRequest) {
+ SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
+
+ request.setPage(httpRequest.mandatoryParamAsInt(Param.PAGE));
+ request.setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE));
+
+ return request.build();
+ }
+
+ private static SearchProjectsWsResponse buildResponse(SearchProjectsRequest request, SearchResults searchResults) {
+ SearchProjectsWsResponse.Builder response = SearchProjectsWsResponse.newBuilder();
+
+ response.setPaging(Common.Paging.newBuilder()
+ .setPageIndex(request.getPage())
+ .setPageSize(request.getPageSize())
+ .setTotal(searchResults.total));
+
+ Function<ComponentDto, Component> dbToWsComponent = new DbToWsComponent();
+
+ searchResults.projects
+ .stream()
+ .map(dbToWsComponent)
+ .forEach(response::addComponents);
+
+ return response.build();
+ }
+
+ private static class DbToWsComponent implements Function<ComponentDto, Component> {
+ private final Component.Builder wsComponent;
+
+ private DbToWsComponent() {
+ this.wsComponent = Component.newBuilder();
+ }
+
+ @Override
+ public Component apply(ComponentDto dbComponent) {
+ return wsComponent
+ .clear()
+ .setId(dbComponent.uuid())
+ .setKey(dbComponent.key())
+ .setName(dbComponent.name())
+ .build();
+ }
+ }
+
+ private static class SearchResults {
+ private final List<ComponentDto> projects;
+ private final int total;
+
+ private SearchResults(List<ComponentDto> projects, long total) {
+ this.projects = projects;
+ this.total = (int) total;
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 0da17b5439f..008c5dc4def 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -175,6 +175,7 @@ import org.sonar.server.plugins.ws.PluginWSCommons;
import org.sonar.server.plugins.ws.PluginsWs;
import org.sonar.server.plugins.ws.UninstallAction;
import org.sonar.server.plugins.ws.UpdatesAction;
+import org.sonar.server.project.es.ProjectsEsModule;
import org.sonar.server.project.ws.ProjectsWsModule;
import org.sonar.server.projectlink.ws.ProjectLinksModule;
import org.sonar.server.property.InternalPropertiesImpl;
@@ -434,6 +435,7 @@ public class PlatformLevel4 extends PlatformLevel {
// components
ProjectsWsModule.class,
+ ProjectsEsModule.class,
ComponentsWsModule.class,
DefaultComponentFinder.class,
DefaultRubyComponentService.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndex.java b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndex.java
new file mode 100644
index 00000000000..04486442f25
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndex.java
@@ -0,0 +1,55 @@
+/*
+ * 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.project.es;
+
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+import org.sonar.server.es.BaseIndex;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_NAME;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndex extends BaseIndex {
+
+ public ProjectMeasuresIndex(EsClient client) {
+ super(client);
+ }
+
+ public SearchIdResult<String> search(SearchOptions searchOptions) {
+ QueryBuilder condition = QueryBuilders.matchAllQuery();
+
+ SearchRequestBuilder request = getClient()
+ .prepareSearch(INDEX_PROJECT_MEASURES)
+ .setTypes(TYPE_PROJECT_MEASURES)
+ .setFetchSource(false)
+ .setQuery(condition)
+ .setFrom(searchOptions.getOffset())
+ .setSize(searchOptions.getLimit())
+ .addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);
+
+ return new SearchIdResult<>(request.get(), id -> id);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndexDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndexDefinition.java
new file mode 100644
index 00000000000..c2f75e8ab62
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndexDefinition.java
@@ -0,0 +1,53 @@
+/*
+ * 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.project.es;
+
+import org.sonar.api.config.Settings;
+import org.sonar.server.es.IndexDefinition;
+import org.sonar.server.es.NewIndex;
+
+public class ProjectMeasuresIndexDefinition implements IndexDefinition {
+
+ public static final String INDEX_PROJECT_MEASURES = "projectmeasures";
+ public static final String TYPE_PROJECT_MEASURES = "projectmeasures";
+ public static final String FIELD_KEY = "key";
+ public static final String FIELD_NAME = "name";
+ public static final String FIELD_ANALYSED_AT = "analysedAt";
+
+ private final Settings settings;
+
+ public ProjectMeasuresIndexDefinition(Settings settings) {
+ this.settings = settings;
+ }
+
+ @Override
+ public void define(IndexDefinitionContext context) {
+ NewIndex index = context.create(INDEX_PROJECT_MEASURES);
+ index.refreshHandledByIndexer();
+ index.configureShards(settings, 5);
+
+ NewIndex.NewIndexType mapping = index.createType(TYPE_PROJECT_MEASURES);
+ mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
+ mapping.stringFieldBuilder(FIELD_NAME).enableSorting().enableGramSearch().build();
+ mapping.createDateTimeField(FIELD_ANALYSED_AT);
+ // do not store document but only indexation of information
+ mapping.setEnableSource(false);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectsEsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectsEsModule.java
new file mode 100644
index 00000000000..d1070ae1e04
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectsEsModule.java
@@ -0,0 +1,32 @@
+/*
+ * 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.project.es;
+
+import org.sonar.core.platform.Module;
+
+public class ProjectsEsModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ ProjectMeasuresIndexDefinition.class,
+ ProjectMeasuresIndex.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/es/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/project/es/package-info.java
new file mode 100644
index 00000000000..53f33d2f155
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/es/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.project.es;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
index d017bb1c013..335e224b39b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
@@ -24,6 +24,7 @@ public class KeyExamples {
public static final String KEY_FILE_EXAMPLE_002 = "another_project:/src/foo/Foo.php";
public static final String KEY_PROJECT_EXAMPLE_001 = "my_project";
public static final String KEY_PROJECT_EXAMPLE_002 = "another_project";
+ public static final String KEY_PROJECT_EXAMPLE_003 = "third_project";
public static final String KEY_DEVELOPER_EXAMPLE_001 = "DEV:ada@lovelace.com";
private KeyExamples() {
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/component/ws/search_projects-example.json b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/search_projects-example.json
new file mode 100644
index 00000000000..14dc2fc75ed
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/search_projects-example.json
@@ -0,0 +1,24 @@
+{
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 3
+ },
+ "components": [
+ {
+ "id": "AU-Tpxb--iU5OvuD2FLy",
+ "key": "my_project",
+ "name": "My Project 1"
+ },
+ {
+ "id": "AU-TpxcA-iU5OvuD2FLz",
+ "key": "another_project",
+ "name": "My Project 2"
+ },
+ {
+ "id": "AU-TpxcA-iU5OvuD2FL0",
+ "key": "third_project",
+ "name": "My Project 3"
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
index 7d5b4443f9c..a126f8c6182 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
@@ -29,6 +29,6 @@ public class ComponentsWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ComponentsWsModule().configure(container);
- assertThat(container.size()).isEqualTo(10 + 2);
+ assertThat(container.size()).isEqualTo(11 + 2);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
new file mode 100644
index 00000000000..2d501b0dfe7
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.component.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.MapSettings;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.project.es.ProjectMeasuresDoc;
+import org.sonar.server.project.es.ProjectMeasuresIndex;
+import org.sonar.server.project.es.ProjectMeasuresIndexDefinition;
+import org.sonar.server.ws.KeyExamples;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsComponents.Component;
+import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
+import org.sonarqube.ws.client.component.SearchProjectsRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.ComponentTesting.newDeveloper;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.ComponentTesting.newView;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class SearchProjectsActionTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ ComponentDbTester componentDb = new ComponentDbTester(db);
+ DbClient dbClient = db.getDbClient();
+
+ WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, new ProjectMeasuresIndex(es.client())));
+
+ SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
+
+ @Test
+ public void json_example() {
+ insertProjectInDbAndEs(newProjectDto()
+ .setUuid(Uuids.UUID_EXAMPLE_01)
+ .setKey(KeyExamples.KEY_PROJECT_EXAMPLE_001)
+ .setName("My Project 1"));
+ insertProjectInDbAndEs(newProjectDto()
+ .setUuid(Uuids.UUID_EXAMPLE_02)
+ .setKey(KeyExamples.KEY_PROJECT_EXAMPLE_002)
+ .setName("My Project 2"));
+ insertProjectInDbAndEs(newProjectDto()
+ .setUuid(Uuids.UUID_EXAMPLE_03)
+ .setKey(KeyExamples.KEY_PROJECT_EXAMPLE_003)
+ .setName("My Project 3"));
+
+ String result = ws.newRequest().execute().getInput();
+
+ assertJson(result).withStrictArrayOrder().isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ @Test
+ public void order_by_name_case_insensitive() {
+ insertProjectInDbAndEs(newProjectDto().setName("Maven"));
+ insertProjectInDbAndEs(newProjectDto().setName("Apache"));
+ insertProjectInDbAndEs(newProjectDto().setName("guava"));
+
+ SearchProjectsWsResponse result = call(request);
+
+ assertThat(result.getComponentsList()).extracting(Component::getName)
+ .containsExactly("Apache", "guava", "Maven");
+ }
+
+ @Test
+ public void paginate_result() {
+ IntStream.rangeClosed(1, 9).forEach(i -> insertProjectInDbAndEs(newProjectDto().setName("PROJECT-" + i)));
+
+ SearchProjectsWsResponse result = call(request.setPage(2).setPageSize(3));
+
+ assertThat(result.getPaging().getPageIndex()).isEqualTo(2);
+ assertThat(result.getPaging().getPageSize()).isEqualTo(3);
+ assertThat(result.getPaging().getTotal()).isEqualTo(9);
+ assertThat(result.getComponentsCount()).isEqualTo(3);
+ assertThat(result.getComponentsList())
+ .extracting(Component::getName)
+ .containsExactly("PROJECT-4", "PROJECT-5", "PROJECT-6");
+ }
+
+ @Test
+ public void empty_result() {
+ SearchProjectsWsResponse result = call(request);
+
+ assertThat(result.getComponentsCount()).isEqualTo(0);
+ Common.Paging paging = result.getPaging();
+ assertThat(paging.getPageIndex()).isEqualTo(1);
+ assertThat(paging.getPageSize()).isEqualTo(100);
+ assertThat(paging.getTotal()).isEqualTo(0);
+ }
+
+ @Test
+ public void return_only_projects() {
+ ComponentDto project = newProjectDto().setName("SonarQube");
+ ComponentDto directory = newDirectory(project, "path");
+ insertProjectInDbAndEs(project);
+ componentDb.insertComponents(newModuleDto(project), newView(), newDeveloper("Sonar Developer"), directory, newFileDto(project, directory));
+
+ SearchProjectsWsResponse result = call(request);
+
+ assertThat(result.getComponentsCount()).isEqualTo(1);
+ assertThat(result.getComponents(0).getName()).isEqualTo("SonarQube");
+ }
+
+ @Test
+ public void fail_if_page_size_greater_than_500() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ call(request.setPageSize(501));
+ }
+
+ private SearchProjectsWsResponse call(SearchProjectsRequest.Builder requestBuilder) {
+ SearchProjectsRequest wsRequest = requestBuilder.build();
+ TestRequest httpRequest = ws.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF);
+
+ httpRequest.setParam(Param.PAGE, String.valueOf(wsRequest.getPage()));
+ httpRequest.setParam(Param.PAGE_SIZE, String.valueOf(wsRequest.getPageSize()));
+
+ try {
+ return SearchProjectsWsResponse.parseFrom(httpRequest.execute().getInputStream());
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action def = ws.getDef();
+
+ assertThat(def.key()).isEqualTo("search_projects");
+ assertThat(def.since()).isEqualTo("6.2");
+ assertThat(def.isInternal()).isTrue();
+ assertThat(def.isPost()).isFalse();
+ assertThat(def.responseExampleAsString()).isNotEmpty();
+ }
+
+ private void insertProjectInDbAndEs(ComponentDto project) {
+ componentDb.insertComponent(project);
+ try {
+ es.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES,
+ new ProjectMeasuresDoc().setId(project.uuid()).setKey(project.key()).setName(project.name()));
+ } catch (Exception e) {
+ Throwables.propagate(e);
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresDoc.java b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresDoc.java
new file mode 100644
index 00000000000..c2e733dd1ad
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresDoc.java
@@ -0,0 +1,81 @@
+/*
+ * 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.project.es;
+
+import java.util.Date;
+import java.util.HashMap;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.server.es.BaseDoc;
+
+public class ProjectMeasuresDoc extends BaseDoc {
+
+ public ProjectMeasuresDoc() {
+ super(new HashMap<>(4));
+ }
+
+ @Override
+ public String getId() {
+ return getField("_id");
+ }
+
+ @Override
+ public String getRouting() {
+ return null;
+ }
+
+ @Override
+ public String getParent() {
+ return null;
+ }
+
+ public ProjectMeasuresDoc setId(String s) {
+ setField("_id", s);
+ return this;
+ }
+
+ public String getKey() {
+ return getField(ProjectMeasuresIndexDefinition.FIELD_KEY);
+ }
+
+ public ProjectMeasuresDoc setKey(String s) {
+ setField(ProjectMeasuresIndexDefinition.FIELD_KEY, s);
+ return this;
+ }
+
+ public String getName() {
+ return getField(ProjectMeasuresIndexDefinition.FIELD_NAME);
+ }
+
+ public ProjectMeasuresDoc setName(String s) {
+ setField(ProjectMeasuresIndexDefinition.FIELD_NAME, s);
+ return this;
+ }
+
+ @CheckForNull
+ public Date getAnalysedAt() {
+ return getFieldAsDate(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT);
+ }
+
+ public ProjectMeasuresDoc setAnalysedAt(@Nullable Date d) {
+ setField(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT, d);
+ return this;
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresIndexTest.java
new file mode 100644
index 00000000000..f67c6bbb43c
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresIndexTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.project.es;
+
+import com.google.common.base.Throwables;
+import java.util.List;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.MapSettings;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndexTest {
+
+ @Rule
+ public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
+
+ private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client());
+
+ @Test
+ public void search_sort_by_name_case_insensitive() {
+ addDocs(newDoc("P1", "K1", "Windows"),
+ newDoc("P3", "K3", "apachee"),
+ newDoc("P2", "K2", "Apache"));
+
+ List<String> result = underTest.search(new SearchOptions()).getIds();
+
+ assertThat(result).containsExactly("P2", "P3", "P1");
+ }
+
+ @Test
+ public void search_paginate_results() {
+ IntStream.rangeClosed(1, 9)
+ .forEach(i -> addDocs(newDoc("P" + i, "K" + i, "P" + i)));
+
+ SearchIdResult<String> result = underTest.search(new SearchOptions().setPage(2, 3));
+
+ assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
+ assertThat(result.getTotal()).isEqualTo(9);
+ }
+
+ private static ProjectMeasuresDoc newDoc(String uuid, String key, String name) {
+ return new ProjectMeasuresDoc()
+ .setId(uuid)
+ .setKey(key)
+ .setName(name);
+ }
+
+ private void addDocs(ProjectMeasuresDoc... docs) {
+ try {
+ es.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, docs);
+ } catch (Exception e) {
+ Throwables.propagate(e);
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectsEsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectsEsModuleTest.java
new file mode 100644
index 00000000000..6ff53ec14c6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectsEsModuleTest.java
@@ -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.project.es;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectsEsModuleTest {
+ @Test
+ public void verify_count_of_added_components() {
+ ComponentContainer container = new ComponentContainer();
+ new ProjectsEsModule().configure(container);
+ assertThat(container.size()).isEqualTo(2 + 2);
+ }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java
new file mode 100644
index 00000000000..03292b2bf32
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.component;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class SearchProjectsRequest {
+ public static final int MAX_PAGE_SIZE = 500;
+ public static final int DEFAULT_PAGE_SIZE = 100;
+
+ private final int page;
+ private final int pageSize;
+
+ private SearchProjectsRequest(Builder builder) {
+ this.page = builder.page;
+ this.pageSize = builder.pageSize;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Integer page;
+ private Integer pageSize;
+
+ private Builder() {
+ // enforce static factory method
+ }
+
+ public Builder setPage(int page) {
+ this.page = page;
+ return this;
+ }
+
+ public Builder setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ public SearchProjectsRequest build() {
+ if (page == null) {
+ page = 1;
+ }
+ if (pageSize == null) {
+ pageSize = DEFAULT_PAGE_SIZE;
+ }
+
+ checkArgument(pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE);
+
+ return new SearchProjectsRequest(this);
+ }
+ }
+}
diff --git a/sonar-ws/src/main/protobuf/ws-components.proto b/sonar-ws/src/main/protobuf/ws-components.proto
index a14966e8acd..4feaf85aa0a 100644
--- a/sonar-ws/src/main/protobuf/ws-components.proto
+++ b/sonar-ws/src/main/protobuf/ws-components.proto
@@ -57,6 +57,12 @@ message BulkUpdateKeyWsResponse {
}
}
+// WS api/components/search_projects
+message SearchProjectsWsResponse {
+ optional sonarqube.ws.commons.Paging paging = 1;
+ repeated Component components = 2;
+}
+
message Component {
optional string id = 1;
optional string key = 2;
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java
new file mode 100644
index 00000000000..1db380272ee
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.component;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SearchProjectsRequestTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ SearchProjectsRequest.Builder underTest = SearchProjectsRequest.builder();
+
+ @Test
+ public void default_page_values() {
+ SearchProjectsRequest result = underTest.build();
+
+ assertThat(result.getPage()).isEqualTo(1);
+ assertThat(result.getPageSize()).isEqualTo(100);
+ }
+
+ @Test
+ public void handle_paging_limit_values() {
+ SearchProjectsRequest result = underTest.setPageSize(500).build();
+
+ assertThat(result.getPage()).isEqualTo(1);
+ assertThat(result.getPageSize()).isEqualTo(500);
+ }
+
+ @Test
+ public void fail_if_page_size_greater_than_500() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Page size must not be greater than 500");
+
+ underTest.setPageSize(501).build();
+ }
+}