diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-10-10 16:21:55 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-12 16:45:33 +0200 |
commit | b081169a1d0ed70c29678d28271baea8885494db (patch) | |
tree | dec26c9de431f31ff34294f0dc6a6779b7afc03f | |
parent | 55dfa5865f5de6ae44f37dcf28985e7501d16fe5 (diff) | |
download | sonarqube-b081169a1d0ed70c29678d28271baea8885494db.tar.gz sonarqube-b081169a1d0ed70c29678d28271baea8885494db.zip |
SONAR-8221 Create WS api/components/search_projects paginate and sort
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(); + } +} |