diff options
author | Jenkins CI <ci@sonarsource.com> | 2016-04-21 08:01:09 +0200 |
---|---|---|
committer | Jenkins CI <ci@sonarsource.com> | 2016-04-21 08:01:09 +0200 |
commit | 91584ca578a35442467d075ec42151b409778503 (patch) | |
tree | 3e1ea4ea59a2d4e4d05de51affca73172fe65f78 | |
parent | 6c65ff90687ebe7d184864b0d665f8a51aecfc7a (diff) | |
parent | 9d70ff56e7d45ca6d26ee5725cdea6288585358b (diff) | |
download | sonarqube-91584ca578a35442467d075ec42151b409778503.tar.gz sonarqube-91584ca578a35442467d075ec42151b409778503.zip |
Automatic merge from branch-5.5
* origin/branch-5.5:
SONAR-7553 use api/ce/activity_status to get number of pending and failing tasks
SONAR-7553 WS api/ce/activity_status display background tasks related metrics for UI
SONAR-7553 DAO of CE_QUEUE count number of rows by status
SONAR-7553 DAO of CE_ACTIVITY count number of tasks still failing
SONAR-7187 WS api/ce/activity stop using count(1) on DB table CE_QUEUE
SONAR-7553 Create composite DB index CE_ACTIVITY(is_last, status)
SONAR-7187 do not usage pagination
SONAR-7187 Remove paging from api/ce/activity
30 files changed, 597 insertions, 199 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java index 69781bece76..1a15334bd80 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java @@ -37,8 +37,8 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.Paging; import org.sonar.api.web.UserRole; +import org.sonar.ce.taskprocessor.CeTaskProcessor; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; @@ -50,10 +50,8 @@ 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.ce.taskprocessor.CeTaskProcessor; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.user.UserSession; -import org.sonarqube.ws.Common; import org.sonarqube.ws.WsCe; import org.sonarqube.ws.WsCe.ActivityResponse; import org.sonarqube.ws.client.ce.ActivityWsRequest; @@ -63,7 +61,6 @@ import static java.util.Collections.singletonList; import static org.apache.commons.lang.StringUtils.defaultString; import static org.sonar.api.utils.DateUtils.parseDateQuietly; import static org.sonar.api.utils.DateUtils.parseDateTimeQuietly; -import static org.sonar.api.utils.Paging.offset; import static org.sonar.server.ws.WsUtils.checkRequest; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_ID; @@ -75,6 +72,7 @@ import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_STATUS; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_TYPE; public class ActivityAction implements CeWsAction { + private static final int OFFSET = 0; private static final int MAX_PAGE_SIZE = 1000; private final UserSession userSession; @@ -98,7 +96,8 @@ public class ActivityAction implements CeWsAction { WebService.NewAction action = controller.createAction("activity") .setDescription(format("Search for tasks.<br> " + "Requires the system administration permission, " + - "or project administration permission if %s is set.", PARAM_COMPONENT_ID)) + "or project administration permission if %s is set.<br/>" + + "Since 5.5, it's no more possible to specify the page parameter", PARAM_COMPONENT_ID)) .setResponseExample(getClass().getResource("activity-example.json")) .setHandler(this) .setSince("5.2"); @@ -147,7 +146,11 @@ public class ActivityAction implements CeWsAction { action.createParam(PARAM_MAX_EXECUTED_AT) .setDescription("Maximum date of end of task processing (inclusive)") .setExampleValue(DateUtils.formatDateTime(new Date())); - action.addPagingParams(100, MAX_PAGE_SIZE); + action.createParam(Param.PAGE) + .setDescription("Deprecated parameter") + .setDeprecatedSince("5.5") + .setDeprecatedKey("pageIndex"); + action.addPageSize(100, MAX_PAGE_SIZE); } @Override @@ -165,21 +168,18 @@ public class ActivityAction implements CeWsAction { return buildResponse( singletonList(taskSearchedById.get()), Collections.<WsCe.Task>emptyList(), - Paging.forPageIndex(1).withPageSize(request.getPageSize()).andTotal(1)); + request.getPageSize()); } CeTaskQuery query = buildQuery(dbSession, request); checkPermissions(query); - TaskResult queuedTasks = loadQueuedTasks(dbSession, request, query); - TaskResult pastTasks = loadPastTasks(dbSession, request, query, queuedTasks.total); + Iterable<WsCe.Task> queuedTasks = loadQueuedTasks(dbSession, request, query); + Iterable<WsCe.Task> pastTasks = loadPastTasks(dbSession, request, query); return buildResponse( - queuedTasks.tasks, - pastTasks.tasks, - Paging.forPageIndex(request.getPage()) - .withPageSize(request.getPageSize()) - .andTotal(queuedTasks.total + pastTasks.total)); - + queuedTasks, + pastTasks, + request.getPageSize()); } finally { dbClient.closeSession(dbSession); } @@ -234,24 +234,14 @@ public class ActivityAction implements CeWsAction { } } - private TaskResult loadQueuedTasks(DbSession dbSession, ActivityWsRequest request, CeTaskQuery query) { - int total = dbClient.ceQueueDao().countByQuery(dbSession, query); - List<CeQueueDto> dtos = dbClient.ceQueueDao().selectByQueryInDescOrder(dbSession, query, - Paging.forPageIndex(request.getPage()) - .withPageSize(request.getPageSize()) - .andTotal(total)); - Iterable<WsCe.Task> tasks = formatter.formatQueue(dbSession, dtos); - return new TaskResult(tasks, total); + private Iterable<WsCe.Task> loadQueuedTasks(DbSession dbSession, ActivityWsRequest request, CeTaskQuery query) { + List<CeQueueDto> dtos = dbClient.ceQueueDao().selectByQueryInDescOrder(dbSession, query, request.getPageSize()); + return formatter.formatQueue(dbSession, dtos); } - private TaskResult loadPastTasks(DbSession dbSession, ActivityWsRequest request, CeTaskQuery query, int totalQueuedTasks) { - int total = dbClient.ceActivityDao().countByQuery(dbSession, query); - // we have to take into account the total number of queue tasks found - int offset = Math.max(0, offset(request.getPage(), request.getPageSize()) - totalQueuedTasks); - List<CeActivityDto> dtos = dbClient.ceActivityDao().selectByQuery(dbSession, query, offset, request.getPageSize()); - Iterable<WsCe.Task> ceTasks = formatter.formatActivity(dbSession, dtos); - - return new TaskResult(ceTasks, total); + private Iterable<WsCe.Task> loadPastTasks(DbSession dbSession, ActivityWsRequest request, CeTaskQuery query) { + List<CeActivityDto> dtos = dbClient.ceActivityDao().selectByQuery(dbSession, query, OFFSET, request.getPageSize()); + return formatter.formatActivity(dbSession, dtos); } private void checkPermissions(CeTaskQuery query) { @@ -285,29 +275,23 @@ public class ActivityAction implements CeWsAction { return userSession.hasPermission(GlobalPermissions.SYSTEM_ADMIN) || userSession.hasComponentUuidPermission(UserRole.ADMIN, componentUuid); } - private static ActivityResponse buildResponse(Iterable<WsCe.Task> queuedTasks, Iterable<WsCe.Task> pastTasks, Paging paging) { + private static ActivityResponse buildResponse(Iterable<WsCe.Task> queuedTasks, Iterable<WsCe.Task> pastTasks, int pageSize) { WsCe.ActivityResponse.Builder wsResponseBuilder = WsCe.ActivityResponse.newBuilder(); int nbInsertedTasks = 0; for (WsCe.Task queuedTask : queuedTasks) { - if (nbInsertedTasks < paging.pageSize()) { + if (nbInsertedTasks < pageSize) { wsResponseBuilder.addTasks(queuedTask); nbInsertedTasks++; } } for (WsCe.Task pastTask : pastTasks) { - if (nbInsertedTasks < paging.pageSize()) { + if (nbInsertedTasks < pageSize) { wsResponseBuilder.addTasks(pastTask); nbInsertedTasks++; } } - - wsResponseBuilder.setPaging(Common.Paging.newBuilder() - .setPageIndex(paging.pageIndex()) - .setPageSize(paging.pageSize()) - .setTotal(paging.total())); - return wsResponseBuilder.build(); } @@ -320,7 +304,6 @@ public class ActivityAction implements CeWsAction { .setMinSubmittedAt(request.param(PARAM_MIN_SUBMITTED_AT)) .setMaxExecutedAt(request.param(PARAM_MAX_EXECUTED_AT)) .setOnlyCurrents(request.paramAsBoolean(PARAM_ONLY_CURRENTS)) - .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)); checkRequest(activityWsRequest.getComponentId() == null || activityWsRequest.getQuery() == null, "%s and %s must not be set at the same time", @@ -329,14 +312,4 @@ public class ActivityAction implements CeWsAction { return activityWsRequest; } - - private static class TaskResult { - private final Iterable<WsCe.Task> tasks; - private final int total; - - private TaskResult(Iterable<WsCe.Task> tasks, int total) { - this.tasks = tasks; - this.total = total; - } - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java new file mode 100644 index 00000000000..b8e09db8b56 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java @@ -0,0 +1,127 @@ +/* + * 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.ce.ws; + +import com.google.common.base.Optional; +import javax.annotation.Nullable; +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.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeActivityDto; +import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.KeyExamples; +import org.sonarqube.ws.WsCe.ActivityStatusWsResponse; +import org.sonarqube.ws.client.ce.ActivityStatusWsRequest; + +import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_KEY; + +public class ActivityStatusAction implements CeWsAction { + private final UserSession userSession; + private final DbClient dbClient; + private final ComponentFinder componentFinder; + + public ActivityStatusAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder) { + this.userSession = userSession; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller + .createAction("activity_status") + .setDescription("Return CE activity related metrics.<br>" + + "Requires 'Administer System' permission or 'Administer' rights on the specified project.") + .setSince("5.5") + .setResponseExample(getClass().getResource("activity_status-example.json")) + .setInternal(true) + .setHandler(this); + + action.createParam(PARAM_COMPONENT_ID) + .setDescription("Id of the component (project) to filter on") + .setExampleValue(Uuids.UUID_EXAMPLE_03); + action.createParam(PARAM_COMPONENT_KEY) + .setDescription("Key of the component (project) to filter on") + .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); + } + + @Override + public void handle(Request request, Response response) throws Exception { + ActivityStatusWsResponse activityStatusResponse = doHandle(toWsRequest(request)); + writeProtobuf(activityStatusResponse, request, response); + } + + private ActivityStatusWsResponse doHandle(ActivityStatusWsRequest request) { + DbSession dbSession = dbClient.openSession(false); + try { + Optional<ComponentDto> component = searchComponent(dbSession, request); + String componentUuid = component.isPresent() ? component.get().uuid() : null; + checkPermissions(componentUuid); + int pendingCount = dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid); + int failingCount = dbClient.ceActivityDao().countLastByStatusAndComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid); + + return ActivityStatusWsResponse.newBuilder() + .setPending(pendingCount) + .setFailing(failingCount) + .build(); + } finally { + dbClient.closeSession(dbSession); + } + } + + private Optional<ComponentDto> searchComponent(DbSession dbSession, ActivityStatusWsRequest request) { + ComponentDto component = null; + if (hasComponentInRequest(request)) { + component = componentFinder.getByUuidOrKey(dbSession, request.getComponentId(), request.getComponentKey(), COMPONENT_ID_AND_KEY); + } + return Optional.fromNullable(component); + } + + private void checkPermissions(@Nullable String componentUuid) { + if (componentUuid == null) { + userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN); + } else { + userSession.checkComponentUuidPermission(UserRole.ADMIN, componentUuid); + } + } + + private static boolean hasComponentInRequest(ActivityStatusWsRequest request) { + return request.getComponentId() != null || request.getComponentKey() != null; + } + + private static ActivityStatusWsRequest toWsRequest(Request request) { + return ActivityStatusWsRequest.newBuilder() + .setComponentId(request.param(PARAM_COMPONENT_ID)) + .setComponentKey(request.param(PARAM_COMPONENT_KEY)) + .build(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java index 73b04cf18ba..ac527405405 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java @@ -27,6 +27,7 @@ public class CeWsModule extends Module { add( CeWs.class, ActivityAction.class, + ActivityStatusAction.class, CancelAction.class, CancelAllAction.class, IsQueueEmptyWs.class, diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/activity_status-example.json b/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/activity_status-example.json new file mode 100644 index 00000000000..c57d76edd38 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/activity_status-example.json @@ -0,0 +1,4 @@ +{ + "pending": 2, + "failing": 5 +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java index 59b6d464533..eeb4690492e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java @@ -34,15 +34,15 @@ import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; +import org.sonar.ce.log.CeLogging; +import org.sonar.ce.log.LogFileRef; +import org.sonar.ce.taskprocessor.CeTaskProcessor; import org.sonar.core.permission.GlobalPermissions; 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.ce.log.CeLogging; -import org.sonar.ce.log.LogFileRef; -import org.sonar.ce.taskprocessor.CeTaskProcessor; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; @@ -169,28 +169,23 @@ public class ActivityActionTest { } @Test - public void paginate_results() { + public void limit_results() { userSession.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); insertActivity("T1", "PROJECT_1", CeActivityDto.Status.SUCCESS); insertActivity("T2", "PROJECT_2", CeActivityDto.Status.FAILED); insertQueue("T3", "PROJECT_1", CeQueueDto.Status.IN_PROGRESS); - assertPage(1, 1, 3, asList("T3")); - assertPage(2, 1, 3, asList("T2")); - assertPage(1, 10, 3, asList("T3", "T2", "T1")); - assertPage(2, 10, 3, Collections.<String>emptyList()); + assertPage(1, asList("T3")); + assertPage(2, asList("T3", "T2")); + assertPage(10, asList("T3", "T2", "T1")); + assertPage(0, Collections.<String>emptyList()); } - private void assertPage(int pageIndex, int pageSize, int expectedTotal, List<String> expectedOrderedTaskIds) { + private void assertPage(int pageSize, List<String> expectedOrderedTaskIds) { ActivityResponse activityResponse = call(ws.newRequest() - .setParam(Param.PAGE, Integer.toString(pageIndex)) .setParam(Param.PAGE_SIZE, Integer.toString(pageSize)) .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING")); - assertThat(activityResponse.getPaging().getPageIndex()).isEqualTo(pageIndex); - assertThat(activityResponse.getPaging().getPageSize()).isEqualTo(pageSize); - assertThat(activityResponse.getPaging().getTotal()).isEqualTo(expectedTotal); - assertThat(activityResponse.getTasksCount()).isEqualTo(expectedOrderedTaskIds.size()); for (int i = 0; i < expectedOrderedTaskIds.size(); i++) { String expectedTaskId = expectedOrderedTaskIds.get(i); diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java new file mode 100644 index 00000000000..ad438ce56a4 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java @@ -0,0 +1,191 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.ce.ws; + +import com.google.common.base.Throwables; +import java.io.IOException; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeActivityDto; +import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +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.MediaTypes; +import org.sonarqube.ws.WsCe; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_KEY; + +public class ActivityStatusActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + ComponentDbTester componentDb = new ComponentDbTester(db); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + + WsActionTester ws = new WsActionTester(new ActivityStatusAction(userSession, dbClient, new ComponentFinder(dbClient))); + + @Before + public void setUp() { + userSession.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + } + + @Test + public void json_example() { + dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-1").setStatus(CeQueueDto.Status.PENDING)); + dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-2").setStatus(CeQueueDto.Status.PENDING)); + for (int i = 0; i < 5; i++) { + dbClient.ceActivityDao().insert(dbSession, new CeActivityDto(newCeQueueDto("ce-activity-uuid-" + i)) + .setStatus(CeActivityDto.Status.FAILED)); + } + db.commit(); + + String result = ws.newRequest().execute().getInput(); + + assertJson(result).isSimilarTo(getClass().getResource("activity_status-example.json")); + } + + @Test + public void status_for_a_project_as_project_admin() { + String projectUuid = "project-uuid"; + String anotherProjectUuid = "another-project-uuid"; + userSession.login().addProjectUuidPermissions(UserRole.ADMIN, projectUuid); + componentDb.insertComponent(newProjectDto(projectUuid)); + componentDb.insertComponent(newProjectDto(anotherProjectUuid)); + // pending tasks returned + insertInQueue(CeQueueDto.Status.PENDING, projectUuid); + insertInQueue(CeQueueDto.Status.PENDING, projectUuid); + // other tasks not returned + insertInQueue(CeQueueDto.Status.IN_PROGRESS, projectUuid); + insertInQueue(CeQueueDto.Status.PENDING, anotherProjectUuid); + insertInQueue(CeQueueDto.Status.PENDING, null); + // only one last activity for a given project + insertActivity(CeActivityDto.Status.SUCCESS, projectUuid); + insertActivity(CeActivityDto.Status.CANCELED, projectUuid); + insertActivity(CeActivityDto.Status.FAILED, projectUuid); + insertActivity(CeActivityDto.Status.FAILED, projectUuid); + insertActivity(CeActivityDto.Status.FAILED, anotherProjectUuid); + + WsCe.ActivityStatusWsResponse result = call(projectUuid); + + assertThat(result.getPending()).isEqualTo(2); + assertThat(result.getFailing()).isEqualTo(1); + } + + @Test + public void empty_status() { + WsCe.ActivityStatusWsResponse result = call(); + + assertThat(result.getPending()).isEqualTo(0); + assertThat(result.getFailing()).isEqualTo(0); + } + + @Test + public void fail_if_component_uuid_and_key_are_provided() { + ComponentDto project = newProjectDto(); + componentDb.insertComponent(project); + expectedException.expect(IllegalArgumentException.class); + + callByComponentUuidOrComponentKey(project.uuid(), project.key()); + } + + @Test + public void fail_if_component_uuid_is_unknown() { + expectedException.expect(NotFoundException.class); + + call("unknown-uuid"); + } + + @Test + public void fail_if_component_key_is_unknown() { + expectedException.expect(NotFoundException.class); + + callByComponentKey("unknown-key"); + } + + private void insertInQueue(CeQueueDto.Status status, @Nullable String componentUuid) { + dbClient.ceQueueDao().insert(dbSession, newCeQueueDto(Uuids.createFast()) + .setStatus(status) + .setComponentUuid(componentUuid)); + db.commit(); + } + + private void insertActivity(CeActivityDto.Status status, @Nullable String componentUuid) { + dbClient.ceActivityDao().insert(dbSession, new CeActivityDto( + newCeQueueDto(Uuids.createFast()) + .setComponentUuid(componentUuid)) + .setStatus(status)); + db.commit(); + } + + private WsCe.ActivityStatusWsResponse call() { + return callByComponentUuidOrComponentKey(null, null); + } + + private WsCe.ActivityStatusWsResponse call(String componentUuid) { + return callByComponentUuidOrComponentKey(componentUuid, null); + } + + private WsCe.ActivityStatusWsResponse callByComponentKey(String componentKey) { + return callByComponentUuidOrComponentKey(null, componentKey); + } + + private WsCe.ActivityStatusWsResponse callByComponentUuidOrComponentKey(@Nullable String componentUuid, @Nullable String componentKey) { + TestRequest request = ws.newRequest() + .setMediaType(MediaTypes.PROTOBUF); + if (componentUuid != null) { + request.setParam(PARAM_COMPONENT_ID, componentUuid); + } + if (componentKey != null) { + request.setParam(PARAM_COMPONENT_KEY, componentKey); + } + + try { + return WsCe.ActivityStatusWsResponse.parseFrom(request.execute().getInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java index 75eb84a98e0..ddcd9422a37 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java @@ -21,7 +21,6 @@ package org.sonar.server.ce.ws; import org.junit.Test; import org.sonar.core.platform.ComponentContainer; -import org.sonar.server.ce.ws.CeWsModule; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +30,6 @@ public class CeWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new CeWsModule().configure(container); - assertThat(container.size()).isEqualTo(11 + 2 /* injected by ComponentContainer */); + assertThat(container.size()).isEqualTo(12 + 2 /* injected by ComponentContainer */); } } diff --git a/server/sonar-web/src/main/js/api/ce.js b/server/sonar-web/src/main/js/api/ce.js index e46eb236026..66c6b3dc8ab 100644 --- a/server/sonar-web/src/main/js/api/ce.js +++ b/server/sonar-web/src/main/js/api/ce.js @@ -30,6 +30,15 @@ export function getActivity (data) { return $.get(url, data); } +export function getStatus (componentId) { + const url = window.baseUrl + '/api/ce/activity_status'; + const data = {}; + if (componentId) { + Object.assign(data, { componentId }); + } + return getJSON(url, data); +} + export function getTask (id) { const url = window.baseUrl + '/api/ce/task'; return getJSON(url, { id }).then(r => r.task); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js index 3e2ec7e3510..aa9515725fd 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js @@ -25,7 +25,6 @@ import Header from './Header'; import StatsContainer from '../containers/StatsContainer'; import SearchContainer from '../containers/SearchContainer'; import TasksContainer from '../containers/TasksContainer'; -import ListFooterContainer from '../containers/ListFooterContainer'; export default class BackgroundTasksApp extends Component { componentDidMount () { @@ -69,7 +68,6 @@ export default class BackgroundTasksApp extends Component { <StatsContainer/> <SearchContainer/> <TasksContainer/> - <ListFooterContainer/> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js deleted file mode 100644 index 048068f63f5..00000000000 --- a/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { connect } from 'react-redux'; - -import ListFooter from '../../../components/shared/list-footer'; - -function mapStateToProps (state) { - return { - ready: !state.fetching, - total: state.total, - count: state.tasks.length - }; -} - -export default connect( - mapStateToProps -)(ListFooter); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js b/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js index b3d54560da2..095f659f0ca 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js @@ -18,12 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import _ from 'underscore'; -import { getTypes, getActivity, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce'; +import { getTypes, getActivity, getStatus, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce'; import { STATUSES, ALL_TYPES, CURRENTS, DEBOUNCE_DELAY } from '../constants'; -const PAGE_SIZE = 1000; - export const INIT = 'INIT'; export const REQUEST_TASKS = 'REQUEST_TASKS'; export const RECEIVE_TASKS = 'RECEIVE_TASKS'; @@ -48,11 +46,10 @@ export function requestTasks (filters) { }; } -export function receiveTasks (tasks, total) { +export function receiveTasks (tasks) { return { type: RECEIVE_TASKS, - tasks, - total + tasks }; } @@ -145,8 +142,6 @@ function fetchTasks (filters) { const { component } = getState(); const parameters = mapFiltersToParameters(filters); - parameters.ps = PAGE_SIZE; - if (component) { parameters.componentId = component.id; } @@ -155,17 +150,15 @@ function fetchTasks (filters) { return Promise.all([ getActivity(parameters), - getActivity({ ps: 1, onlyCurrents: true, status: STATUSES.FAILED }), - getActivity({ ps: 1, status: STATUSES.PENDING }) + getStatus(parameters.componentId) ]).then(responses => { - const [activity, failingActivity, pendingActivity] = responses; + const [activity, status] = responses; const tasks = activity.tasks; - const total = activity.paging.total; - dispatch(receiveTasks(tasks, total)); + dispatch(receiveTasks(tasks)); - const pendingCount = pendingActivity.paging.total; - const failingCount = failingActivity.paging.total; + const pendingCount = status.pending; + const failingCount = status.failing; dispatch(receiveStats({ pendingCount, failingCount })); }); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js b/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js index 82ada158894..5ac2f53ee93 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js @@ -31,7 +31,6 @@ import { DEFAULT_FILTERS } from '../constants'; export const initialState = { fetching: false, tasks: [], - total: 0, types: [], @@ -69,8 +68,7 @@ export default function (state = initialState, action = {}) { return { ...state, fetching: false, - tasks: action.tasks, - total: action.total + tasks: action.tasks }; case UPDATE_QUERY: return { diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1125_add_index_ce_activity_islast_status.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1125_add_index_ce_activity_islast_status.rb new file mode 100644 index 00000000000..b4d4d180cae --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1125_add_index_ce_activity_islast_status.rb @@ -0,0 +1,31 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2014 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 5.5 +# SONAR-7187 & SONAR-7553 +# +class AddIndexCeActivityIslastStatus < ActiveRecord::Migration + + def self.up + add_index :ce_activity, ['is_last','status'], :name => 'ce_activity_islast_status' + end + +end 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 e32a1be1535..91f155ee3ca 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 @@ -22,6 +22,7 @@ package org.sonar.db.ce; import com.google.common.base.Optional; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; import org.apache.ibatis.session.RowBounds; import org.sonar.api.utils.System2; import org.sonar.db.Dao; @@ -72,11 +73,8 @@ public class CeActivityDao implements Dao { return mapper(dbSession).selectByQuery(query, offset, pageSize); } - public int countByQuery(DbSession dbSession, CeTaskQuery query) { - if (query.isShortCircuitedByComponentUuids()) { - return 0; - } - return mapper(dbSession).countByQuery(query); + public int countLastByStatusAndComponentUuid(DbSession dbSession, CeActivityDto.Status status, @Nullable String componentUuid) { + return mapper(dbSession).countLastByStatusAndComponentUuid(status, componentUuid); } private static CeActivityMapper mapper(DbSession dbSession) { diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityMapper.java b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityMapper.java index ea4652d4185..8bdaa57b1af 100644 --- a/sonar-db/src/main/java/org/sonar/db/ce/CeActivityMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/ce/CeActivityMapper.java @@ -21,6 +21,7 @@ package org.sonar.db.ce; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.session.RowBounds; @@ -37,7 +38,7 @@ public interface CeActivityMapper { List<CeActivityDto> selectOlderThan(@Param("beforeDate") long beforeDate); - int countByQuery(@Param("query") CeTaskQuery query); + int countLastByStatusAndComponentUuid(@Param("status") CeActivityDto.Status status, @Nullable @Param("componentUuid") String componentUuid); void insert(CeActivityDto dto); diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeQueueDao.java b/sonar-db/src/main/java/org/sonar/db/ce/CeQueueDao.java index 9670c8c8068..283b3823443 100644 --- a/sonar-db/src/main/java/org/sonar/db/ce/CeQueueDao.java +++ b/sonar-db/src/main/java/org/sonar/db/ce/CeQueueDao.java @@ -21,8 +21,8 @@ package org.sonar.db.ce; import com.google.common.base.Optional; import java.util.List; +import javax.annotation.Nullable; import org.apache.ibatis.session.RowBounds; -import org.sonar.api.utils.Paging; import org.sonar.api.utils.System2; import org.sonar.db.Dao; import org.sonar.db.DbSession; @@ -48,14 +48,14 @@ public class CeQueueDao implements Dao { return mapper(session).selectAllInAscOrder(); } - public List<CeQueueDto> selectByQueryInDescOrder(DbSession dbSession, CeTaskQuery query, Paging paging) { + public List<CeQueueDto> selectByQueryInDescOrder(DbSession dbSession, CeTaskQuery query, int pageSize) { if (query.isShortCircuitedByComponentUuids() || query.isOnlyCurrents() || query.getMaxExecutedAt() != null) { return emptyList(); } - return mapper(dbSession).selectByQueryInDescOrder(query, new RowBounds(paging.offset(), paging.pageSize())); + return mapper(dbSession).selectByQueryInDescOrder(query, new RowBounds(0, pageSize)); } public int countByQuery(DbSession dbSession, CeTaskQuery query) { @@ -101,11 +101,11 @@ public class CeQueueDao implements Dao { } public int countByStatus(DbSession dbSession, CeQueueDto.Status status) { - return mapper(dbSession).countByStatus(status); + return mapper(dbSession).countByStatusAndComponentUuid(status, null); } - public int countAll(DbSession dbSession) { - return mapper(dbSession).countAll(); + public int countByStatusAndComponentUuid(DbSession dbSession, CeQueueDto.Status status, @Nullable String componentUuid) { + return mapper(dbSession).countByStatusAndComponentUuid(status, componentUuid); } public Optional<CeQueueDto> peek(DbSession session) { diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeQueueMapper.java b/sonar-db/src/main/java/org/sonar/db/ce/CeQueueMapper.java index 62fca4a7ff1..aa0645987dd 100644 --- a/sonar-db/src/main/java/org/sonar/db/ce/CeQueueMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/ce/CeQueueMapper.java @@ -40,9 +40,7 @@ public interface CeQueueMapper { @CheckForNull CeQueueDto selectByUuid(@Param("uuid") String uuid); - int countByStatus(@Param("status") CeQueueDto.Status status); - - int countAll(); + int countByStatusAndComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("componentUuid") String componentUuid); void insert(CeQueueDto dto); diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java index 4b0508ef27f..8f51cc19382 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java +++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java @@ -29,7 +29,7 @@ import org.sonar.db.MyBatis; public class DatabaseVersion { - public static final int LAST_VERSION = 1124; + public static final int LAST_VERSION = 1125; /** * The minimum supported version which can be upgraded. Lower 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 08247b4faa1..69e58b22989 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 @@ -74,11 +74,6 @@ WHERE rn BETWEEN (#{offset} * #{pageSize} + 1) AND ((#{offset} + 1) * #{pageSize}) </select> - <select id="countByQuery" parameterType="map" resultType="int"> - select count(ca.id) - <include refid="sqlSelectByQuery" /> - </select> - <sql id="sqlSelectByQuery"> from ce_activity ca <where> @@ -114,6 +109,16 @@ from ce_activity ca where ca.created_at < #{beforeDate,jdbcType=BIGINT} </select> + + <select id="countLastByStatusAndComponentUuid" resultType="int"> + select count(1) + from ce_activity + where status=#{status} + and is_last=${_true} + <if test="componentUuid!=null"> + and component_uuid=#{componentUuid} + </if> + </select> <insert id="insert" parameterType="org.sonar.db.ce.CeActivityDto" useGeneratedKeys="false"> insert into ce_activity diff --git a/sonar-db/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml b/sonar-db/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml index c9924353c1a..e3d71cc85ac 100644 --- a/sonar-db/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml @@ -29,8 +29,13 @@ where cq.uuid=#{uuid} </select> - <select id="countByStatus" parameterType="org.sonar.db.ce.CeQueueDto$Status" resultType="int"> - select count(1) from ce_queue where status=#{status} + <select id="countByStatusAndComponentUuid" parameterType="map" resultType="int"> + select count(1) + from ce_queue + where status=#{status} + <if test="componentUuid!=null"> + and component_uuid=#{componentUuid} + </if> </select> <select id="countAll" resultType="int"> diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql index 9b36923ad6c..72e8889de26 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -402,6 +402,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1121'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1122'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1123'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1124'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1125'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl index 1bbd3df88ce..4b00eea977c 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -669,6 +669,8 @@ CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID"); CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID"); +CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS"); + CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID"); CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID"); @@ -678,3 +680,5 @@ CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH"); CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME"); CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY"); + +CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS"); diff --git a/sonar-db/src/test/java/org/sonar/db/DbTester.java b/sonar-db/src/test/java/org/sonar/db/DbTester.java index 64c10150619..d58f519899f 100644 --- a/sonar-db/src/test/java/org/sonar/db/DbTester.java +++ b/sonar-db/src/test/java/org/sonar/db/DbTester.java @@ -155,7 +155,7 @@ public class DbTester extends ExternalResource { /** * Returns the number of rows in the table. Example: - * <pre>int issues = countTable("issues")</pre> + * <pre>int issues = countRowsOfTable("issues")</pre> */ public int countRowsOfTable(String tableName) { Preconditions.checkArgument(StringUtils.containsNone(tableName, " "), "Parameter must be the name of a table. Got " + tableName); diff --git a/sonar-db/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java b/sonar-db/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java index bb92b76097b..c233b42c15d 100644 --- a/sonar-db/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java @@ -28,10 +28,13 @@ import javax.annotation.Nonnull; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.ce.CeActivityDto.Status.FAILED; +import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS; import static org.sonar.db.ce.CeTaskTypes.REPORT; public class CeActivityDaoTest { @@ -40,6 +43,7 @@ public class CeActivityDaoTest { @Rule public DbTester db = DbTester.create(system2); + DbSession dbSession = db.getSession(); CeActivityDao underTest = new CeActivityDao(system2); @@ -75,7 +79,7 @@ public class CeActivityDaoTest { assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue(); // two tasks on PROJECT_1, the most recent one is TASK_3 - insert("TASK_3", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED); + insert("TASK_3", REPORT, "PROJECT_1", FAILED); assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse(); assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue(); assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue(); @@ -91,7 +95,7 @@ public class CeActivityDaoTest { @Test public void test_selectByQuery() { insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS); - insert("TASK_2", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED); + insert("TASK_2", REPORT, "PROJECT_1", FAILED); insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS); insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS); @@ -141,36 +145,6 @@ public class CeActivityDaoTest { } @Test - public void test_countByQuery() { - insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS); - insert("TASK_2", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED); - insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS); - insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS); - - // no filters - CeTaskQuery query = new CeTaskQuery(); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(4); - - // select by component uuid - query = new CeTaskQuery().setComponentUuid("PROJECT_1"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(2); - - // select by status - query = new CeTaskQuery().setStatuses(singletonList(CeActivityDto.Status.SUCCESS.name())); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(3); - - // select by type - query = new CeTaskQuery().setType(REPORT); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(3); - query = new CeTaskQuery().setType("views"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(1); - - // select by multiple conditions - query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setComponentUuid("PROJECT_1"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(1); - } - - @Test public void selectByQuery_no_results_if_shortcircuited_by_component_uuids() { insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS); @@ -180,15 +154,6 @@ public class CeActivityDaoTest { } @Test - public void countByQuery_no_results_if_shortcircuited_by_component_uuids() { - insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS); - - CeTaskQuery query = new CeTaskQuery(); - query.setComponentUuids(Collections.<String>emptyList()); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(0); - } - - @Test public void select_and_count_by_date() { insertWithDates("UUID1", 1_450_000_000_000L, 1_470_000_000_000L); insertWithDates("UUID2", 1_460_000_000_000L, 1_480_000_000_000L); @@ -196,19 +161,16 @@ public class CeActivityDaoTest { // search by min submitted date CeTaskQuery query = new CeTaskQuery().setMinSubmittedAt(1_455_000_000_000L); assertThat(underTest.selectByQuery(db.getSession(), query, 0, 5)).extracting("uuid").containsOnly("UUID2"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(1); // search by max executed date query = new CeTaskQuery().setMaxExecutedAt(1_475_000_000_000L); assertThat(underTest.selectByQuery(db.getSession(), query, 0, 5)).extracting("uuid").containsOnly("UUID1"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(1); // search by both dates query = new CeTaskQuery() .setMinSubmittedAt(1_400_000_000_000L) .setMaxExecutedAt(1_475_000_000_000L); assertThat(underTest.selectByQuery(db.getSession(), query, 0, 5)).extracting("uuid").containsOnly("UUID1"); - assertThat(underTest.countByQuery(db.getSession(), query)).isEqualTo(1); } @@ -253,6 +215,22 @@ public class CeActivityDaoTest { assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").isPresent()).isTrue(); } + @Test + public void count_last_by_status_and_component_uuid() { + insert("TASK_1", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS); + // component 2 + insert("TASK_2", CeTaskTypes.REPORT, "COMPONENT2", CeActivityDto.Status.SUCCESS); + // status failed + insert("TASK_3", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.FAILED); + // status canceled + insert("TASK_4", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.CANCELED); + insert("TASK_5", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS); + db.commit(); + + assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, "COMPONENT1")).isEqualTo(1); + assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, null)).isEqualTo(2); + } + private void insert(String uuid, String type, String componentUuid, CeActivityDto.Status status) { CeQueueDto queueDto = new CeQueueDto(); queueDto.setUuid(uuid); diff --git a/sonar-db/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java b/sonar-db/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java index 479bd23bf76..30c4694047b 100644 --- a/sonar-db/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java @@ -29,7 +29,6 @@ import java.util.Map; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; -import org.sonar.api.utils.Paging; import org.sonar.api.utils.internal.TestSystem2; import org.sonar.db.DbTester; @@ -140,7 +139,7 @@ public class CeQueueDaoTest { system2.setNow(INIT_TIME + 3_000_000); insert(TASK_UUID_2, COMPONENT_UUID_2, PENDING); - assertThat(underTest.countAll(db.getSession())).isEqualTo(2); + assertThat(db.countRowsOfTable("ce_queue")).isEqualTo(2); verifyCeQueueStatuses(TASK_UUID_1, PENDING, TASK_UUID_2, PENDING); // peek first one @@ -227,7 +226,7 @@ public class CeQueueDaoTest { .setType(CeTaskTypes.REPORT) .setMinSubmittedAt(100_000L); - List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, Paging.forPageIndex(1).withPageSize(1_000).andTotal(1_000)); + List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000); int total = underTest.countByQuery(db.getSession(), query); assertThat(result).extracting("uuid").containsExactly("TASK_5", TASK_UUID_2); @@ -244,7 +243,7 @@ public class CeQueueDaoTest { CeTaskQuery query = new CeTaskQuery().setOnlyCurrents(true); - List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, Paging.forPageIndex(1).withPageSize(1_000).andTotal(1_000)); + List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000); int total = underTest.countByQuery(db.getSession(), query); assertThat(result).isEmpty(); @@ -261,7 +260,7 @@ public class CeQueueDaoTest { CeTaskQuery query = new CeTaskQuery().setMaxExecutedAt(1_000_000_000_000L); - List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, Paging.forPageIndex(1).withPageSize(1_000).andTotal(1_000)); + List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000); int total = underTest.countByQuery(db.getSession(), query); assertThat(result).isEmpty(); @@ -278,13 +277,38 @@ public class CeQueueDaoTest { CeTaskQuery query = new CeTaskQuery().setComponentUuids(Collections.<String>emptyList()); - List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, Paging.forPageIndex(1).withPageSize(1_000).andTotal(1_000)); + List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000); int total = underTest.countByQuery(db.getSession(), query); assertThat(result).isEmpty(); assertThat(total).isEqualTo(0); } + @Test + public void count_by_status_and_component_uuid() { + // task retrieved in the queue + insert(newCeQueueDto(TASK_UUID_1) + .setComponentUuid(COMPONENT_UUID_1) + .setStatus(IN_PROGRESS) + .setTaskType(CeTaskTypes.REPORT) + .setCreatedAt(100_000L)); + // on component uuid 2, not returned + insert(newCeQueueDto(TASK_UUID_2) + .setComponentUuid(COMPONENT_UUID_2) + .setStatus(IN_PROGRESS) + .setTaskType(CeTaskTypes.REPORT) + .setCreatedAt(100_000L)); + // pending status, not returned + insert(newCeQueueDto(TASK_UUID_3) + .setComponentUuid(COMPONENT_UUID_1) + .setStatus(PENDING) + .setTaskType(CeTaskTypes.REPORT) + .setCreatedAt(100_000L)); + + assertThat(underTest.countByStatusAndComponentUuid(db.getSession(), IN_PROGRESS, COMPONENT_UUID_1)).isEqualTo(1); + assertThat(underTest.countByStatus(db.getSession(), IN_PROGRESS)).isEqualTo(2); + } + private void insert(CeQueueDto dto) { underTest.insert(db.getSession(), dto); db.commit(); @@ -315,12 +339,13 @@ public class CeQueueDaoTest { }); } - private void verifyCeQueueStatuses(String taskUuid1, CeQueueDto.Status taskStatus1, String taskUuid2, CeQueueDto.Status taskStatus2, String taskUuid3, CeQueueDto.Status taskStatus3) { - verifyCeQueueStatuses(new String[]{taskUuid1, taskUuid2, taskUuid3}, new CeQueueDto.Status[]{taskStatus1, taskStatus2, taskStatus3}); + private void verifyCeQueueStatuses(String taskUuid1, CeQueueDto.Status taskStatus1, String taskUuid2, CeQueueDto.Status taskStatus2, String taskUuid3, + CeQueueDto.Status taskStatus3) { + verifyCeQueueStatuses(new String[] {taskUuid1, taskUuid2, taskUuid3}, new CeQueueDto.Status[] {taskStatus1, taskStatus2, taskStatus3}); } private void verifyCeQueueStatuses(String taskUuid1, CeQueueDto.Status taskStatus1, String taskUuid2, CeQueueDto.Status taskStatus2) { - verifyCeQueueStatuses(new String[]{taskUuid1, taskUuid2}, new CeQueueDto.Status[]{taskStatus1, taskStatus2}); + verifyCeQueueStatuses(new String[] {taskUuid1, taskUuid2}, new CeQueueDto.Status[] {taskStatus1, taskStatus2}); } private void verifyCeQueueStatuses(String[] taskUuids, CeQueueDto.Status[] statuses) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index 484956d7a68..3a9b87fe478 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -349,12 +349,21 @@ public interface WebService extends Definable<WebService.Context> { * Note the maximum is a documentation only feature. It does not check anything. */ public NewAction addPagingParams(int defaultPageSize, int maxPageSize) { + addPageParam(); + addPageSize(defaultPageSize, maxPageSize); + return this; + } + + public NewAction addPageParam() { createParam(Param.PAGE) .setDescription("1-based page number") .setExampleValue("42") .setDeprecatedKey("pageIndex") .setDefaultValue("1"); + return this; + } + public NewAction addPageSize(int defaultPageSize, int maxPageSize) { createParam(Param.PAGE_SIZE) .setDescription("Page size. Must be greater than 0 and less than " + maxPageSize) .setExampleValue("20") diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ActivityStatusWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ActivityStatusWsRequest.java new file mode 100644 index 00000000000..66407145b75 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ActivityStatusWsRequest.java @@ -0,0 +1,71 @@ +/* + * 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.ce; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class ActivityStatusWsRequest { + private final String componentId; + private final String componentKey; + + private ActivityStatusWsRequest(Builder builder) { + this.componentId = builder.componentId; + this.componentKey = builder.componentKey; + } + + @CheckForNull + public String getComponentId() { + return componentId; + } + + @CheckForNull + public String getComponentKey() { + return componentKey; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private String componentId; + private String componentKey; + + private Builder() { + // enforce newBuilder() use for instantiation + } + + public Builder setComponentId(@Nullable String componentId) { + this.componentId = componentId; + return this; + } + + public Builder setComponentKey(@Nullable String componentKey) { + this.componentKey = componentKey; + return this; + } + + public ActivityStatusWsRequest build() { + return new ActivityStatusWsRequest(this); + } + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java index 1871cd800d9..460ddb53faa 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java @@ -28,6 +28,7 @@ import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsConnector; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_KEY; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_MAX_EXECUTED_AT; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_MIN_SUBMITTED_AT; import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_ONLY_CURRENTS; @@ -71,4 +72,13 @@ public class CeService extends BaseService { public WsCe.TaskResponse task(String id) { return call(new GetRequest(path("task")).setParam("id", id), WsCe.TaskResponse.parser()); } + + public WsCe.ActivityStatusWsResponse activityStatus(ActivityStatusWsRequest request) { + return call( + new GetRequest(path("activity_status")) + .setParam(PARAM_COMPONENT_ID, request.getComponentId()) + .setParam(PARAM_COMPONENT_KEY, request.getComponentKey()), + WsCe.ActivityStatusWsResponse.parser()); + } + } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeWsParameters.java index 3b4fc97e4f1..e2254619a2a 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeWsParameters.java @@ -22,6 +22,7 @@ package org.sonarqube.ws.client.ce; public class CeWsParameters { public static final String PARAM_COMPONENT_ID = "componentId"; + public static final String PARAM_COMPONENT_KEY = "componentKey"; public static final String PARAM_COMPONENT_QUERY = "componentQuery"; public static final String PARAM_TYPE = "type"; public static final String PARAM_STATUS = "status"; diff --git a/sonar-ws/src/main/protobuf/ws-ce.proto b/sonar-ws/src/main/protobuf/ws-ce.proto index 1db7111abcf..4440851e45a 100644 --- a/sonar-ws/src/main/protobuf/ws-ce.proto +++ b/sonar-ws/src/main/protobuf/ws-ce.proto @@ -39,10 +39,17 @@ message TaskResponse { // GET api/ce/activity message ActivityResponse { - optional sonarqube.ws.commons.Paging paging = 1; + // paging has been deprecated in 5.5 + optional sonarqube.ws.commons.Paging unusedPaging = 1; repeated Task tasks = 2; } +// GET api/ce/activity_status +message ActivityStatusWsResponse { + optional int32 pending = 1; + optional int32 failing = 2; +} + // GET api/ce/project message ProjectResponse { repeated Task queue = 1; |