--- /dev/null
+/*
+ * 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();
+ }
+}
add(
CeWs.class,
ActivityAction.class,
+ ActivityStatusAction.class,
CancelAction.class,
CancelAllAction.class,
IsQueueEmptyWs.class,
--- /dev/null
+{
+ "pending": 2,
+ "failing": 5
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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;
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 */);
}
}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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;
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());
+ }
+
}
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";
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;