aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2016-12-12 17:16:05 +0100
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2016-12-13 14:48:31 +0100
commita01c99bb547c5f9318eb536f03e473e8cb6f1126 (patch)
treeb830158f84979d9ba83b8bd43617326949be9775 /server
parentfb9f4f8314cc44d45aef2f41a82520d969080444 (diff)
downloadsonarqube-a01c99bb547c5f9318eb536f03e473e8cb6f1126.tar.gz
sonarqube-a01c99bb547c5f9318eb536f03e473e8cb6f1126.zip
SONAR-8467 Create WS api/project_analyses/search
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java154
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResponseBuilder.java92
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResults.java129
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/search-example.json42
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java270
7 files changed, 691 insertions, 2 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java
index e4c43303e22..08de37424ed 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java
@@ -24,6 +24,7 @@ import org.sonar.core.platform.Module;
import org.sonar.server.projectanalysis.ws.CreateEventAction;
import org.sonar.server.projectanalysis.ws.DeleteEventAction;
import org.sonar.server.projectanalysis.ws.ProjectAnalysesWs;
+import org.sonar.server.projectanalysis.ws.SearchAction;
import org.sonar.server.projectanalysis.ws.UpdateEventAction;
public class ProjectAnalysisModule extends Module {
@@ -35,7 +36,8 @@ public class ProjectAnalysisModule extends Module {
// actions
CreateEventAction.class,
UpdateEventAction.class,
- DeleteEventAction.class);
+ DeleteEventAction.class,
+ SearchAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java
new file mode 100644
index 00000000000..39235dd8e56
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java
@@ -0,0 +1,154 @@
+/*
+ * 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.projectanalysis.ws;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
+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.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.KeyExamples;
+import org.sonarqube.ws.ProjectAnalyses;
+import org.sonarqube.ws.client.projectanalysis.EventCategory;
+import org.sonarqube.ws.client.projectanalysis.SearchRequest;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.core.util.stream.Collectors.toOneElement;
+import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
+import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.OTHER;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_CATEGORY;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_PROJECT;
+import static org.sonarqube.ws.client.projectanalysis.SearchRequest.DEFAULT_PAGE_SIZE;
+
+public class SearchAction implements ProjectAnalysesWsAction {
+ private final DbClient dbClient;
+ private final ComponentFinder componentFinder;
+ private final UserSession userSession;
+
+ public SearchAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.componentFinder = componentFinder;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("search")
+ .setDescription("Search a project analyses and attached events.<br>" +
+ "Requires the following permission: 'Browse' on the specified project")
+ .setSince("6.3")
+ .setResponseExample(getClass().getResource("search-example.json"))
+ .setHandler(this);
+
+ action.addPagingParams(DEFAULT_PAGE_SIZE, 500);
+
+ action.createParam(PARAM_PROJECT)
+ .setDescription("Project key")
+ .setRequired(true)
+ .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
+
+ action.createParam(PARAM_CATEGORY)
+ .setDescription("Event category. Filter analyses that have at least one event of the category specified.")
+ .setPossibleValues(EnumSet.allOf(EventCategory.class))
+ .setExampleValue(OTHER.name());
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ ProjectAnalyses.SearchResponse searchResponse = Stream.of(request)
+ .map(toWsRequest())
+ .map(this::search)
+ .map(new SearchResponseBuilder().buildWsResponse())
+ .collect(toOneElement());
+ writeProtobuf(searchResponse, request, response);
+ }
+
+ private static Function<Request, SearchRequest> toWsRequest() {
+ return request -> {
+ String category = request.param(PARAM_CATEGORY);
+ return SearchRequest.builder()
+ .setProject(request.mandatoryParam(PARAM_PROJECT))
+ .setCategory(category == null ? null : EventCategory.valueOf(category))
+ .setPage(request.mandatoryParamAsInt(Param.PAGE))
+ .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
+ .build();
+ };
+ }
+
+ private SearchResults search(SearchRequest request) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return Stream.of(SearchResults.builder(dbSession, request))
+ .peek(addProject())
+ .peek(checkPermission())
+ .peek(addAnalyses())
+ .peek(addEvents())
+ .map(SearchResults.Builder::build)
+ .collect(toOneElement());
+ }
+ }
+
+ private Consumer<SearchResults.Builder> addAnalyses() {
+ return data -> {
+ SnapshotQuery dbQuery = new SnapshotQuery()
+ .setComponentUuid(data.getProjectUuid())
+ .setStatus(SnapshotDto.STATUS_PROCESSED)
+ .setSort(BY_DATE, DESC);
+ data.setAnalyses(dbClient.snapshotDao().selectAnalysesByQuery(data.getDbSession(), dbQuery));
+ };
+ }
+
+ private Consumer<SearchResults.Builder> addEvents() {
+ return data -> {
+ List<String> analyses = data.getAnalyses().stream().map(SnapshotDto::getUuid).collect(Collectors.toList());
+ data.setEvents(dbClient.eventDao().selectByAnalysisUuids(data.getDbSession(), analyses));
+ };
+ }
+
+ private Consumer<SearchResults.Builder> checkPermission() {
+ return data -> userSession.checkComponentUuidPermission(UserRole.USER, data.getProjectUuid());
+ }
+
+ private Consumer<SearchResults.Builder> addProject() {
+ return data -> {
+ ComponentDto project = componentFinder.getByKey(data.getDbSession(), data.getRequest().getProject());
+ checkArgument(Scopes.PROJECT.equals(project.scope()) && Qualifiers.PROJECT.equals(project.qualifier()), "A project is required");
+ data.setProjectUuid(project.uuid());
+ };
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResponseBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResponseBuilder.java
new file mode 100644
index 00000000000..0dcd59deb19
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResponseBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * 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.projectanalysis.ws;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonarqube.ws.ProjectAnalyses.Analysis;
+import org.sonarqube.ws.ProjectAnalyses.Event;
+import org.sonarqube.ws.ProjectAnalyses.SearchResponse;
+
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.fromLabel;
+
+class SearchResponseBuilder {
+ private final Analysis.Builder analysisBuilder;
+ private final Event.Builder eventBuilder;
+
+ SearchResponseBuilder() {
+ this.analysisBuilder = Analysis.newBuilder();
+ this.eventBuilder = Event.newBuilder();
+ }
+
+ Function<SearchResults, SearchResponse> buildWsResponse() {
+ return searchResults -> Stream.of(SearchResponse.newBuilder())
+ .peek(addAnalyses(searchResults))
+ .peek(addPagination(searchResults))
+ .map(SearchResponse.Builder::build)
+ .collect(Collectors.toOneElement());
+ }
+
+ private Consumer<SearchResponse.Builder> addAnalyses(SearchResults searchResults) {
+ return response -> searchResults.analyses.stream()
+ .map(dbToWsAnalysis())
+ .peek(addEvents(searchResults))
+ .forEach(response::addAnalyses);
+ }
+
+ private Function<SnapshotDto, Analysis.Builder> dbToWsAnalysis() {
+ return dbAnalysis -> analysisBuilder.clear()
+ .setKey(dbAnalysis.getUuid())
+ .setDate(formatDateTime(dbAnalysis.getCreatedAt()));
+ }
+
+ private Consumer<Analysis.Builder> addEvents(SearchResults searchResults) {
+ return wsAnalysis -> searchResults.eventsByAnalysis.get(wsAnalysis.getKey()).stream()
+ .map(dbToWsEvent())
+ .forEach(wsAnalysis::addEvents);
+ }
+
+ private Function<EventDto, Event.Builder> dbToWsEvent() {
+ return dbEvent -> {
+ Event.Builder wsEvent = eventBuilder.clear()
+ .setKey(dbEvent.getUuid());
+ setNullable(dbEvent.getName(), wsEvent::setName);
+ setNullable(dbEvent.getDescription(), wsEvent::setDescription);
+ setNullable(dbEvent.getCategory(), cat -> wsEvent.setCategory(fromLabel(cat).name()));
+ return wsEvent;
+ };
+ }
+
+ private static Consumer<SearchResponse.Builder> addPagination(SearchResults searchResults) {
+ return response -> response.getPagingBuilder()
+ .setPageIndex(searchResults.paging.pageIndex())
+ .setPageSize(searchResults.paging.pageSize())
+ .setTotal(searchResults.paging.total())
+ .build();
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResults.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResults.java
new file mode 100644
index 00000000000..b70f023a775
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchResults.java
@@ -0,0 +1,129 @@
+/*
+ * 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.projectanalysis.ws;
+
+import com.google.common.collect.ListMultimap;
+import java.util.List;
+import java.util.stream.Stream;
+import org.sonar.api.utils.Paging;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonarqube.ws.client.projectanalysis.SearchRequest;
+
+import static java.util.Objects.requireNonNull;
+
+class SearchResults {
+ final List<SnapshotDto> analyses;
+ final ListMultimap<String, EventDto> eventsByAnalysis;
+ final Paging paging;
+
+ private SearchResults(Builder builder) {
+ this.analyses = builder.analyses;
+ this.eventsByAnalysis = buildEvents(builder.events);
+ this.paging = Paging
+ .forPageIndex(builder.getRequest().getPage())
+ .withPageSize(builder.getRequest().getPageSize())
+ .andTotal(builder.countAnalyses);
+ }
+
+ private ListMultimap<String, EventDto> buildEvents(List<EventDto> events) {
+ return events.stream().collect(Collectors.index(EventDto::getAnalysisUuid));
+ }
+
+ static Builder builder(DbSession dbSession, SearchRequest request) {
+ return new Builder(dbSession, request);
+ }
+
+ static class Builder {
+ private final DbSession dbSession;
+ private final SearchRequest request;
+ private String projectUuid;
+ private List<SnapshotDto> analyses;
+ private int countAnalyses;
+ private List<EventDto> events;
+
+ private Builder(DbSession dbSession, SearchRequest request) {
+ this.dbSession = dbSession;
+ this.request = request;
+ }
+
+ Builder setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ return this;
+ }
+
+ Builder setAnalyses(List<SnapshotDto> analyses) {
+ Stream<SnapshotDto> stream = analyses.stream();
+ // no filter by category, the pagination can be applied
+ if (request.getCategory() == null) {
+ stream = stream
+ .skip(Paging.offset(request.getPage(), request.getPageSize()))
+ .limit(request.getPageSize());
+ }
+
+ this.analyses = stream.collect(Collectors.toList());
+ this.countAnalyses = analyses.size();
+ return this;
+ }
+
+ Builder setEvents(List<EventDto> events) {
+ this.events = events;
+ return this;
+ }
+
+ DbSession getDbSession() {
+ return dbSession;
+ }
+
+ SearchRequest getRequest() {
+ return request;
+ }
+
+ String getProjectUuid() {
+ return projectUuid;
+ }
+
+ List<SnapshotDto> getAnalyses() {
+ return analyses;
+ }
+
+ private void filterByCategory() {
+ ListMultimap<String, String> eventCategoriesByAnalysisUuid = events.stream()
+ .collect(Collectors.index(EventDto::getAnalysisUuid, EventDto::getCategory));
+ this.analyses = analyses.stream()
+ .filter(a -> eventCategoriesByAnalysisUuid.get(a.getUuid()).contains(request.getCategory().getLabel()))
+ .skip(Paging.offset(request.getPage(), request.getPageSize()))
+ .limit(request.getPageSize())
+ .collect(Collectors.toList());
+ }
+
+ SearchResults build() {
+ requireNonNull(analyses);
+ requireNonNull(events);
+ if (request.getCategory() != null) {
+ filterByCategory();
+ }
+ return new SearchResults(this);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/search-example.json
new file mode 100644
index 00000000000..7d602081749
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/search-example.json
@@ -0,0 +1,42 @@
+{
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 2
+ },
+ "analyses": [
+ {
+ "key": "A2",
+ "date": "2016-12-12T17:12:45+0100",
+ "events": [
+ {
+ "key": "E21",
+ "category": "QUALITY_PROFILE",
+ "name": "Quality Profile changed to Sonar Way",
+ },
+ {
+ "key": "E22",
+ "category": "OTHER",
+ "name": "6.3",
+ }
+ ]
+ },
+ {
+ "key": "A1",
+ "date": "2016-12-11T17:12:45+0100",
+ "events": [
+ {
+ "key": "E11",
+ "category": "QUALITY_GATE",
+ "name": "Quality Gate is Red (was Orange)",
+ "description": "Coverage is \u003c 80%"
+ },
+ {
+ "key": "E12",
+ "category": "VERSION",
+ "name": "6.3",
+ }
+ ]
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java
index bd1018524f6..b7841ce9ec2 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java
@@ -31,6 +31,6 @@ public class ProjectAnalysisModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ProjectAnalysisModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 4);
+ assertThat(container.size()).isEqualTo(2 + 5);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java
new file mode 100644
index 00000000000..35d641e2ef0
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.projectanalysis.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.util.List;
+import java.util.function.Function;
+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.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.ProjectAnalyses.Analysis;
+import org.sonarqube.ws.ProjectAnalyses.Event;
+import org.sonarqube.ws.ProjectAnalyses.SearchResponse;
+import org.sonarqube.ws.client.projectanalysis.EventCategory;
+import org.sonarqube.ws.client.projectanalysis.SearchRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.SnapshotTesting.newAnalysis;
+import static org.sonar.db.event.EventTesting.newEvent;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.MediaTypes.PROTOBUF;
+import static org.sonarqube.ws.client.WsRequest.Method.POST;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.OTHER;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.QUALITY_GATE;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.VERSION;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_CATEGORY;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_PROJECT;
+
+public class SearchActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+ @Rule
+ public DbTester db = DbTester.create();
+ private DbClient dbClient = db.getDbClient();
+
+ private WsActionTester ws = new WsActionTester(new SearchAction(dbClient, new ComponentFinder(dbClient), userSession));
+
+ @Test
+ public void json_example() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey(KEY_PROJECT_EXAMPLE_001));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ SnapshotDto a1 = db.components().insertSnapshot(newAnalysis(project).setUuid("A1").setCreatedAt(parseDateTime("2016-12-11T17:12:45+0100").getTime()));
+ SnapshotDto a2 = db.components().insertSnapshot(newAnalysis(project).setUuid("A2").setCreatedAt(parseDateTime("2016-12-12T17:12:45+0100").getTime()));
+ db.events().insertEvent(newEvent(a1).setUuid("E11")
+ .setName("Quality Gate is Red (was Orange)")
+ .setCategory(EventCategory.QUALITY_GATE.getLabel())
+ .setDescription("Coverage is < 80%"));
+ db.events().insertEvent(newEvent(a1).setUuid("E12")
+ .setName("6.3").setCategory(VERSION.getLabel()));
+ db.events().insertEvent(newEvent(a2).setUuid("E21")
+ .setName("Quality Profile changed to Sonar Way")
+ .setCategory(EventCategory.QUALITY_PROFILE.getLabel()));
+ db.events().insertEvent(newEvent(a2).setUuid("E22")
+ .setName("6.3").setCategory(OTHER.getLabel()));
+
+ String result = ws.newRequest()
+ .setParam(PARAM_PROJECT, KEY_PROJECT_EXAMPLE_001)
+ .execute().getInput();
+
+ assertJson(result).isSimilarTo(getClass().getResource("search-example.json"));
+ }
+
+ @Test
+ public void return_analyses_ordered_by_analysis_date() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey("P1"));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ db.components().insertSnapshot(newAnalysis(project).setUuid("A1").setCreatedAt(1_000_000L));
+ db.components().insertSnapshot(newAnalysis(project).setUuid("A2").setCreatedAt(2_000_000L));
+ db.components().insertSnapshot(newAnalysis(project).setUuid("A3").setCreatedAt(3_000_000L));
+
+ List<Analysis> result = call("P1").getAnalysesList();
+
+ assertThat(result).hasSize(3);
+ assertThat(result).extracting(Analysis::getKey, a -> parseDateTime(a.getDate()).getTime()).containsExactly(
+ tuple("A3", 3_000_000L),
+ tuple("A2", 2_000_000L),
+ tuple("A1", 1_000_000L));
+ }
+
+ @Test
+ public void return_only_processed_analyses() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey("P1"));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ db.components().insertSnapshot(newAnalysis(project).setUuid("A1"));
+ db.components().insertSnapshot(newAnalysis(project).setUuid("A2").setStatus(SnapshotDto.STATUS_UNPROCESSED));
+
+ List<Analysis> result = call("P1").getAnalysesList();
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getKey()).isEqualTo("A1");
+ }
+
+ @Test
+ public void return_events() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey("P1"));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ SnapshotDto a1 = db.components().insertSnapshot(newAnalysis(project).setUuid("A1"));
+ SnapshotDto a42 = db.components().insertSnapshot(newAnalysis(newProjectDto()).setUuid("A42"));
+ EventDto e1 = db.events().insertEvent(newEvent(a1).setUuid("E1").setName("N1").setCategory(EventCategory.QUALITY_GATE.getLabel()).setDescription("D1"));
+ EventDto e2 = db.events().insertEvent(newEvent(a1).setUuid("E2").setName("N2").setCategory(VERSION.getLabel()).setDescription("D2"));
+ db.events().insertEvent(newEvent(a42));
+
+ List<Analysis> result = call("P1").getAnalysesList();
+
+ assertThat(result).hasSize(1);
+ List<Event> events = result.get(0).getEventsList();
+ assertThat(events).hasSize(2);
+ assertThat(events).extracting(Event::getKey, wsToDbCategory(), Event::getName, Event::getDescription).containsOnly(
+ tuple(e1.getUuid(), e1.getCategory(), e1.getName(), e1.getDescription()),
+ tuple(e2.getUuid(), e2.getCategory(), e2.getName(), e2.getDescription()));
+ }
+
+ @Test
+ public void paginate_analyses() {
+ ComponentDto project = db.components().insertProject();
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ IntStream.rangeClosed(1, 9).forEach(i -> db.components().insertSnapshot(newAnalysis(project).setCreatedAt(1_000_000L * i).setUuid("A" + i)));
+
+ SearchResponse result = call(SearchRequest.builder()
+ .setProject(project.key())
+ .setPage(2)
+ .setPageSize(3)
+ .build());
+
+ assertThat(result.getAnalysesList()).extracting(Analysis::getKey)
+ .containsExactly("A6", "A5", "A4");
+ }
+
+ @Test
+ public void filter_by_category() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey("P1"));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ SnapshotDto a1 = db.components().insertSnapshot(newAnalysis(project).setUuid("A1"));
+ SnapshotDto a2 = db.components().insertSnapshot(newAnalysis(project).setUuid("A2"));
+ SnapshotDto a42 = db.components().insertSnapshot(newAnalysis(project).setUuid("A42"));
+ db.events().insertEvent(newEvent(a1).setUuid("E11").setCategory(VERSION.getLabel()));
+ db.events().insertEvent(newEvent(a1).setUuid("E12").setCategory(QUALITY_GATE.getLabel()));
+ db.events().insertEvent(newEvent(a2).setUuid("E21").setCategory(QUALITY_GATE.getLabel()));
+ // Analysis A42 doesn't have a quality gate event
+ db.events().insertEvent(newEvent(a42).setCategory(OTHER.getLabel()));
+
+ List<Analysis> result = call(SearchRequest.builder()
+ .setProject("P1")
+ .setCategory(QUALITY_GATE)
+ .build()).getAnalysesList();
+
+ assertThat(result).extracting(Analysis::getKey).containsOnly("A1", "A2");
+ }
+
+ @Test
+ public void paginate_with_filter_on_category() {
+ ComponentDto project = db.components().insertComponent(newProjectDto().setKey("P1"));
+ userSession.addProjectUuidPermissions(UserRole.USER, project.uuid());
+ SnapshotDto a1 = db.components().insertSnapshot(newAnalysis(project).setUuid("A1").setCreatedAt(1_000_000L));
+ SnapshotDto a2 = db.components().insertSnapshot(newAnalysis(project).setUuid("A2").setCreatedAt(2_000_000L));
+ SnapshotDto a42 = db.components().insertSnapshot(newAnalysis(project).setUuid("A42"));
+ db.events().insertEvent(newEvent(a1).setUuid("E11").setCategory(VERSION.getLabel()));
+ db.events().insertEvent(newEvent(a1).setUuid("E12").setCategory(QUALITY_GATE.getLabel()));
+ db.events().insertEvent(newEvent(a2).setUuid("E21").setCategory(QUALITY_GATE.getLabel()));
+ // Analysis A42 doesn't have a quality gate event
+ db.events().insertEvent(newEvent(a42).setCategory(OTHER.getLabel()));
+
+ List<Analysis> result = call(SearchRequest.builder()
+ .setProject("P1")
+ .setCategory(QUALITY_GATE)
+ .setPage(1)
+ .setPageSize(1)
+ .build()).getAnalysesList();
+
+ assertThat(result).extracting(Analysis::getKey).containsOnly("A2");
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("search");
+ assertThat(definition.since()).isEqualTo("6.3");
+ assertThat(definition.responseExampleAsString()).isNotEmpty();
+ assertThat(definition.param(PARAM_PROJECT).isRequired()).isTrue();
+ assertThat(definition.param(PARAM_CATEGORY)).isNotNull();
+ }
+
+ @Test
+ public void fail_if_not_enough_permissions() {
+ userSession.anonymous();
+ ComponentDto project = db.components().insertProject();
+
+ expectedException.expect(ForbiddenException.class);
+
+ call(project.key());
+ }
+
+ @Test
+ public void fail_if_project_does_not_exist() {
+ expectedException.expect(NotFoundException.class);
+
+ call("P1");
+ }
+
+ private static Function<Event, String> wsToDbCategory() {
+ return e -> e == null ? null : EventCategory.valueOf(e.getCategory()).getLabel();
+ }
+
+ private SearchResponse call(@Nullable String project) {
+ SearchRequest.Builder request = SearchRequest.builder();
+ setNullable(project, request::setProject);
+ return call(request.build());
+ }
+
+ private SearchResponse call(SearchRequest wsRequest) {
+ TestRequest request = ws.newRequest()
+ .setMediaType(PROTOBUF)
+ .setMethod(POST.name());
+ setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
+ setNullable(wsRequest.getCategory(), category -> request.setParam(PARAM_CATEGORY, category.name()));
+ setNullable(wsRequest.getPage(), page -> request.setParam(Param.PAGE, String.valueOf(page)));
+ setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(Param.PAGE_SIZE, String.valueOf(pageSize)));
+
+ try {
+ return SearchResponse.parseFrom(request.execute().getInputStream());
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+}