From: Simon Brandhof Date: Fri, 2 Oct 2015 07:46:36 +0000 (+0200) Subject: SONAR-6834 filter components by name in api/ce/activity X-Git-Tag: 5.2-RC1~122 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=744cc2a6ccac1348c1bb21e7807eb063f7d3134e;p=sonarqube.git SONAR-6834 filter components by name in api/ce/activity --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ws/ActivityWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ws/ActivityWsAction.java index 4d495f58acb..4d0dd94271e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/ws/ActivityWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ws/ActivityWsAction.java @@ -19,11 +19,13 @@ */ package org.sonar.server.computation.ws; +import com.google.common.collect.Lists; import java.util.Date; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.ibatis.session.RowBounds; +import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -36,15 +38,23 @@ import org.sonar.db.DbSession; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeActivityQuery; import org.sonar.db.ce.CeTaskTypes; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentDtoFunctions; +import org.sonar.db.component.ComponentQuery; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.user.UserSession; import org.sonar.server.ws.WsUtils; import org.sonarqube.ws.Common; import org.sonarqube.ws.WsCe; +import static java.lang.String.format; +import static org.sonar.server.ws.WsUtils.checkRequest; + public class ActivityWsAction implements CeWsAction { private static final String PARAM_COMPONENT_UUID = "componentId"; + private static final String PARAM_COMPONENT_QUERY = "componentQuery"; private static final String PARAM_TYPE = "type"; private static final String PARAM_STATUS = "status"; private static final String PARAM_ONLY_CURRENTS = "onlyCurrents"; @@ -70,6 +80,9 @@ public class ActivityWsAction implements CeWsAction { action.createParam(PARAM_COMPONENT_UUID) .setDescription("Optional id of the component (project) to filter on") .setExampleValue(Uuids.UUID_EXAMPLE_03); + action.createParam(PARAM_COMPONENT_QUERY) + .setDescription(format("Optional search by component name or key. Must not be set together with %s", PARAM_COMPONENT_UUID)) + .setExampleValue("Apache"); action.createParam(PARAM_STATUS) .setDescription("Optional filter on task status") .setPossibleValues(CeActivityDto.Status.values()); @@ -93,7 +106,9 @@ public class ActivityWsAction implements CeWsAction { public void handle(Request wsRequest, Response wsResponse) throws Exception { DbSession dbSession = dbClient.openSession(false); try { - CeActivityQuery query = readQuery(wsRequest); + CeActivityQuery query = buildQuery(dbSession, wsRequest); + checkPermissions(query); + RowBounds rowBounds = readMyBatisRowBounds(wsRequest); List dtos = dbClient.ceActivityDao().selectByQuery(dbSession, query, rowBounds); int total = dbClient.ceActivityDao().countByQuery(dbSession, query); @@ -111,7 +126,24 @@ public class ActivityWsAction implements CeWsAction { } } - private CeActivityQuery readQuery(Request wsRequest) { + private void checkPermissions(CeActivityQuery query) { + List componentUuids = query.getComponentUuids(); + if (componentUuids != null && componentUuids.size() == 1) { + if (!userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN) && + !userSession.hasComponentUuidPermission(UserRole.ADMIN, componentUuids.get(0))) { + throw new ForbiddenException("Requires administration permission"); + } + } else { + userSession.checkGlobalPermission(UserRole.ADMIN); + } + } + + private CeActivityQuery buildQuery(DbSession dbSession, Request wsRequest) { + String componentUuid = wsRequest.param(PARAM_COMPONENT_UUID); + String componentQuery = wsRequest.param(PARAM_COMPONENT_QUERY); + checkRequest(componentUuid == null || componentQuery == null, + format("Only one of following parameters is accepted: %s or %s", PARAM_COMPONENT_UUID, PARAM_COMPONENT_QUERY)); + CeActivityQuery query = new CeActivityQuery(); query.setType(wsRequest.param(PARAM_TYPE)); query.setOnlyCurrents(wsRequest.mandatoryParamAsBoolean(PARAM_ONLY_CURRENTS)); @@ -123,17 +155,25 @@ public class ActivityWsAction implements CeWsAction { query.setStatus(CeActivityDto.Status.valueOf(status)); } + loadComponentUuids(dbSession, wsRequest, query); + return query; + } + + private void loadComponentUuids(DbSession dbSession, Request wsRequest, CeActivityQuery query) { String componentUuid = wsRequest.param(PARAM_COMPONENT_UUID); - if (componentUuid == null) { - userSession.checkGlobalPermission(UserRole.ADMIN); - } else { - if (userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN) || userSession.hasComponentUuidPermission(UserRole.ADMIN, componentUuid)) { - query.setComponentUuid(componentUuid); - } else { - throw new ForbiddenException("Requires administration permission"); - } + String componentQuery = wsRequest.param(PARAM_COMPONENT_QUERY); + if (componentUuid != null && componentQuery != null) { + throw new BadRequestException(format("Only one of parameters must be set: %s or %s", PARAM_COMPONENT_UUID, PARAM_COMPONENT_QUERY)); + } + + if (componentUuid != null) { + query.setComponentUuid(componentUuid); + } + if (componentQuery != null) { + ComponentQuery componentDtoQuery = new ComponentQuery(dbClient.getDatabase(), componentQuery, Qualifiers.PROJECT, Qualifiers.VIEW); + List componentDtos = dbClient.componentDao().selectByQuery(dbSession, componentDtoQuery, 0, CeActivityQuery.MAX_COMPONENT_UUIDS); + query.setComponentUuids(Lists.transform(componentDtos, ComponentDtoFunctions.toUuid())); } - return query; } private static RowBounds readMyBatisRowBounds(Request wsRequest) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java b/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java index 25a8400f61c..2e010e6e85b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java @@ -32,6 +32,8 @@ import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.plugins.MimeTypes; +import static java.lang.String.format; + public class WsUtils { private WsUtils() { @@ -58,9 +60,9 @@ public class WsUtils { /** * @throws BadRequestException */ - public static void checkRequest(boolean expression, String message) { + public static void checkRequest(boolean expression, String message, Object... messageArguments) { if (!expression) { - throw new BadRequestException(message); + throw new BadRequestException(format(message, messageArguments)); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ws/ActivityWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ws/ActivityWsActionTest.java index ac3374ee6db..7e856fc5ed2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/ws/ActivityWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/ws/ActivityWsActionTest.java @@ -21,11 +21,13 @@ package org.sonar.server.computation.ws; import com.google.common.base.Optional; import java.io.File; +import java.io.IOException; import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; @@ -34,8 +36,10 @@ import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskTypes; +import org.sonar.db.component.ComponentDbTester; import org.sonar.server.computation.log.CeLogging; import org.sonar.server.computation.log.LogFileRef; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.plugins.MimeTypes; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestResponse; @@ -47,15 +51,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentTesting.newProjectDto; public class ActivityWsActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); + ComponentDbTester componentDb = new ComponentDbTester(dbTester); + CeLogging ceLogging = mock(CeLogging.class); TaskFormatter formatter = new TaskFormatter(dbTester.getDbClient(), ceLogging); ActivityWsAction underTest = new ActivityWsAction(userSession, dbTester.getDbClient(), formatter); @@ -93,7 +103,7 @@ public class ActivityWsActionTest { } @Test - public void filter_by_status() { + public void filter_by_status() { userSession.setGlobalPermissions(UserRole.ADMIN); insert("T1", "PROJECT_1", CeActivityDto.Status.SUCCESS); insert("T2", "PROJECT_2", CeActivityDto.Status.FAILED); @@ -157,7 +167,8 @@ public class ActivityWsActionTest { } @Test - public void get_project_activity() { + public void project_administrator_can_access_his_project_activity() { + // no need to be a system admin userSession.addComponentUuidPermission(UserRole.ADMIN, "PROJECT_1", "PROJECT_1"); insert("T1", "PROJECT_1", CeActivityDto.Status.SUCCESS); insert("T2", "PROJECT_2", CeActivityDto.Status.FAILED); @@ -167,7 +178,6 @@ public class ActivityWsActionTest { .setMediaType(MimeTypes.PROTOBUF) .execute(); - // verify the protobuf response WsCe.ActivityResponse activityResponse = Protobuf.read(wsResponse.getInputStream(), WsCe.ActivityResponse.PARSER); assertThat(activityResponse.getTasksCount()).isEqualTo(1); assertThat(activityResponse.getTasks(0).getId()).isEqualTo("T1"); @@ -175,6 +185,39 @@ public class ActivityWsActionTest { assertThat(activityResponse.getTasks(0).getComponentId()).isEqualTo("PROJECT_1"); } + @Test + public void search_activity_by_component_name() throws IOException { + componentDb.insertProjectAndSnapshot(dbTester.getSession(), newProjectDto().setName("apache struts").setUuid("P1")); + componentDb.insertProjectAndSnapshot(dbTester.getSession(), newProjectDto().setName("apache zookeeper").setUuid("P2")); + componentDb.insertProjectAndSnapshot(dbTester.getSession(), newProjectDto().setName("eclipse").setUuid("P3")); + dbTester.commit(); + componentDb.indexProjects(); + userSession.setGlobalPermissions(UserRole.ADMIN); + insert("T1", "P1", CeActivityDto.Status.SUCCESS); + insert("T2", "P2", CeActivityDto.Status.SUCCESS); + insert("T3", "P3", CeActivityDto.Status.SUCCESS); + + TestResponse wsResponse = tester.newRequest() + .setParam("componentQuery", "apac") + .setMediaType(MimeTypes.PROTOBUF) + .execute(); + + WsCe.ActivityResponse activityResponse = WsCe.ActivityResponse.parseFrom(wsResponse.getInputStream()); + assertThat(activityResponse.getTasksList()).extracting("id").containsOnly("T1", "T2"); + } + + @Test + public void fail_if_both_filters_on_component_id_and_name() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Only one of following parameters is accepted: componentId or componentQuery"); + + tester.newRequest() + .setParam("componentId", "ID1") + .setParam("componentQuery", "apache") + .setMediaType(MimeTypes.PROTOBUF) + .execute(); + } + private CeActivityDto insert(String taskUuid, String componentUuid, CeActivityDto.Status status) { CeQueueDto queueDto = new CeQueueDto(); queueDto.setTaskType(CeTaskTypes.REPORT); @@ -184,7 +227,7 @@ public class ActivityWsActionTest { activityDto.setStatus(status); activityDto.setExecutionTimeMs(500L); dbTester.getDbClient().ceActivityDao().insert(dbTester.getSession(), activityDto); - dbTester.getSession().commit(); + dbTester.commit(); return activityDto; } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ws/WsUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/ws/WsUtilsTest.java index a0227286f39..7693a82f8e0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ws/WsUtilsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ws/WsUtilsTest.java @@ -19,7 +19,10 @@ */ package org.sonar.server.ws; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.plugins.MimeTypes; import org.sonarqube.ws.Issues; @@ -27,6 +30,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class WsUtilsTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Test public void write_json_by_default() throws Exception { TestRequest request = new TestRequest(); @@ -54,4 +60,19 @@ public class WsUtilsTest { assertThat(response.stream().mediaType()).isEqualTo(MimeTypes.PROTOBUF); assertThat(Issues.Issue.parseFrom(response.getFlushedOutput()).getKey()).isEqualTo("I1"); } + + @Test + public void checkRequest_ok() { + WsUtils.checkRequest(true, "Missing param: %s", "foo"); + // do not fail + } + + @Test + public void checkRequest_ko() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Missing param: foo"); + + WsUtils.checkRequest(false, "Missing param: %s", "foo"); + } + } diff --git a/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java b/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java index df9fbc98739..84360b3ff8d 100644 --- a/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java +++ b/sonar-db/src/main/java/org/sonar/db/DatabaseUtils.java @@ -40,7 +40,7 @@ import static com.google.common.collect.Lists.newArrayList; public class DatabaseUtils { - private static final int PARTITION_SIZE_FOR_ORACLE = 1000; + public static final int PARTITION_SIZE_FOR_ORACLE = 1000; public static void closeQuietly(@Nullable Connection connection) { if (connection != null) { diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityDao.java b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityDao.java index f54d80f566d..62574c900e4 100644 --- a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityDao.java +++ b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityDao.java @@ -20,6 +20,7 @@ package org.sonar.db.ce; import com.google.common.base.Optional; +import java.util.Collections; import java.util.List; import org.apache.ibatis.session.RowBounds; import org.sonar.api.utils.System2; @@ -64,10 +65,16 @@ public class CeActivityDao implements Dao { * Ordered by id desc -> newest to oldest */ public List selectByQuery(DbSession dbSession, CeActivityQuery query, RowBounds rowBounds) { + if (query.isShortCircuitedByComponentUuids()) { + return Collections.emptyList(); + } return mapper(dbSession).selectByQuery(query, rowBounds); } public int countByQuery(DbSession dbSession, CeActivityQuery query) { + if (query.isShortCircuitedByComponentUuids()) { + return 0; + } return mapper(dbSession).countByQuery(query); } diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityQuery.java b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityQuery.java index 355439d9ae2..942df9e40d3 100644 --- a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityQuery.java +++ b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityQuery.java @@ -19,25 +19,44 @@ */ package org.sonar.db.ce; +import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.db.DatabaseUtils; + +import static java.util.Collections.singletonList; public class CeActivityQuery { + public static final int MAX_COMPONENT_UUIDS = DatabaseUtils.PARTITION_SIZE_FOR_ORACLE; + private boolean onlyCurrents = false; - private String componentUuid; + private List componentUuids; private CeActivityDto.Status status; private String type; private Long minSubmittedAt; private Long maxExecutedAt; @CheckForNull - public String getComponentUuid() { - return componentUuid; + public List getComponentUuids() { + return componentUuids; + } + + public CeActivityQuery setComponentUuids(@Nullable List l) { + this.componentUuids = l; + return this; + } + + public boolean isShortCircuitedByComponentUuids() { + return componentUuids != null && (componentUuids.isEmpty() || componentUuids.size() > MAX_COMPONENT_UUIDS); } - public CeActivityQuery setComponentUuid(@Nullable String componentUuid) { - this.componentUuid = componentUuid; + public CeActivityQuery setComponentUuid(@Nullable String s) { + if (s == null) { + this.componentUuids = null; + } else { + this.componentUuids = singletonList(s); + } return this; } diff --git a/sonar-db/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml b/sonar-db/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml index 3c10c891e62..25d79d14080 100644 --- a/sonar-db/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml @@ -44,39 +44,26 @@ + + from ce_activity ca and ca.is_last=${_true} - - and ca.component_uuid=#{query.componentUuid} + + and ca.component_uuid in + + #{cUuid} + and ca.status=#{query.status} @@ -91,7 +78,7 @@ and ca.executed_at <= #{query.maxExecutedAt} - +