]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11260 Add web service to provide analysis status
authorJanos Gyerik <janos.gyerik@sonarsource.com>
Mon, 17 Sep 2018 15:03:25 +0000 (17:03 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 10 Oct 2018 07:23:04 +0000 (09:23 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml
server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsParameters.java
server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java
sonar-ws/src/main/protobuf/ws-ce.proto

index 71c6c5ea4ba7fb7bdaa2add289f5119abc56f531..9b7dddf3b7e34bf11fea5856497d7c95cee5c490 100644 (file)
@@ -80,6 +80,14 @@ public class CeActivityDao implements Dao {
     return mapper(dbSession).countLastByStatusAndMainComponentUuid(status, mainComponentUuid);
   }
 
+  public Optional<CeActivityDto> selectLastByComponentUuid(DbSession dbSession, String componentUuid) {
+    return Optional.ofNullable(mapper(dbSession).selectLastByComponentUuid(componentUuid));
+  }
+
+  public Optional<CeActivityDto> selectLastByMainComponentUuid(DbSession dbSession, String mainComponentUuid) {
+    return Optional.ofNullable(mapper(dbSession).selectLastByMainComponentUuid(mainComponentUuid));
+  }
+
   private static CeActivityMapper mapper(DbSession dbSession) {
     return dbSession.getMapper(CeActivityMapper.class);
   }
index 42b7fedab7cd578274a7cccc4e72b820d4d93880..e3a39376cf8aac06b45cf85c025bd1ecb9ebc5a2 100644 (file)
@@ -43,4 +43,10 @@ public interface CeActivityMapper {
   void clearMainIsLast(@Param("mainIsLastKey") String mainIsLastKey, @Param("updatedAt") long updatedAt);
 
   void deleteByUuids(@Param("uuids") List<String> uuids);
+
+  @CheckForNull
+  CeActivityDto selectLastByComponentUuid(@Param("componentUuid") String componentUuid);
+
+  @CheckForNull
+  CeActivityDto selectLastByMainComponentUuid(@Param("mainComponentUuid") String mainComponentUuid);
 }
index 61cc9cded153cca2d2535c8bdbed6722ce7af42f..a195f296ba28f402e467cbe9fd478476d7107364 100644 (file)
         #{uuid,jdbcType=VARCHAR}
       </foreach>
   </delete>
+
+  <select id="selectLastByComponentUuid" parameterType="map" resultType="org.sonar.db.ce.CeActivityDto">
+    select
+      <include refid="columns"/>
+    from ce_activity ca
+    left outer join ce_scanner_context csc on
+      ca.uuid = csc.task_uuid
+    where
+      ca.component_uuid = #{componentUuid,jdbcType=VARCHAR}
+      and ca.is_last = ${_true}
+  </select>
+
+  <select id="selectLastByMainComponentUuid" parameterType="map" resultType="org.sonar.db.ce.CeActivityDto">
+    select
+      <include refid="columns"/>
+    from ce_activity ca
+    left outer join ce_scanner_context csc on
+      ca.uuid = csc.task_uuid
+    where
+      ca.main_component_uuid = #{mainComponentUuid,jdbcType=VARCHAR}
+      and ca.main_is_last = ${_true}
+  </select>
+
 </mapper>
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java
new file mode 100644 (file)
index 0000000..280995c
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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 java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeTaskMessageDto;
+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.Ce.AnalysisStatusWsResponse;
+
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_BRANCH;
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT;
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_PULL_REQUEST;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class AnalysisStatusAction implements CeWsAction {
+
+  private final UserSession userSession;
+  private final DbClient dbClient;
+  private final ComponentFinder componentFinder;
+
+  public AnalysisStatusAction(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("analysis_status")
+      .setDescription("Get the analysis status of a given component: a project, branch or pull request.<br>" +
+        "Requires the following permission: 'Browse' on the specified component.")
+      .setSince("7.4")
+      .setResponseExample(getClass().getResource("analysis_status-example.json"))
+      .setInternal(true)
+      .setHandler(this);
+
+    action.createParam(PARAM_COMPONENT)
+      .setRequired(true)
+      .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
+
+    action.createParam(PARAM_BRANCH)
+      .setDescription("Branch key")
+      .setExampleValue(KEY_BRANCH_EXAMPLE_001)
+      .setInternal(true);
+
+    action.createParam(PARAM_PULL_REQUEST)
+      .setDescription("Pull request id")
+      .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+      .setInternal(true);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String componentKey = request.mandatoryParam(PARAM_COMPONENT);
+    String branchKey = request.param(PARAM_BRANCH);
+    String pullRequestKey = request.param(PARAM_PULL_REQUEST);
+
+    checkRequest(branchKey == null || pullRequestKey == null,
+      "Parameters '%s' and '%s' must not be specified at the same time", PARAM_BRANCH, PARAM_PULL_REQUEST);
+
+    doHandle(request, response, componentKey, branchKey, pullRequestKey);
+  }
+
+  private void doHandle(Request request, Response response, String componentKey, @Nullable String branchKey, @Nullable String pullRequestKey) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      ComponentDto component = loadComponent(dbSession, componentKey, branchKey, pullRequestKey);
+      userSession.checkComponentPermission(UserRole.USER, component);
+
+      checkRequest(isProject(component), "Component '%s' must be a project.", componentKey);
+
+      AnalysisStatusWsResponse.Builder responseBuilder = AnalysisStatusWsResponse.newBuilder();
+      CeActivityDto lastActivity = dbClient.ceActivityDao().selectLastByComponentUuid(dbSession, component.uuid()).orElse(null);
+      responseBuilder.setComponent(formatComponent(dbSession, component, lastActivity, branchKey, pullRequestKey));
+
+      writeProtobuf(responseBuilder.build(), request, response);
+    }
+  }
+
+  private static boolean isProject(ComponentDto project) {
+    return Scopes.PROJECT.equals(project.scope()) && Qualifiers.PROJECT.equals(project.qualifier());
+  }
+
+  private ComponentDto loadComponent(DbSession dbSession, String componentKey, @Nullable String branchKey, @Nullable String pullRequestKey) {
+    if (branchKey != null) {
+      return componentFinder.getByKeyAndBranch(dbSession, componentKey, branchKey);
+    }
+    if (pullRequestKey != null) {
+      return componentFinder.getByKeyAndPullRequest(dbSession, componentKey, pullRequestKey);
+    }
+    return componentFinder.getByKey(dbSession, componentKey);
+  }
+
+  private AnalysisStatusWsResponse.Component formatComponent(DbSession dbSession, ComponentDto component, @Nullable CeActivityDto lastActivity,
+    @Nullable String branchKey, @Nullable String pullRequestKey) {
+
+    AnalysisStatusWsResponse.Component.Builder builder = AnalysisStatusWsResponse.Component.newBuilder()
+      .setOrganization(getOrganizationKey(dbSession, component))
+      .setKey(component.getKey())
+      .setName(component.name());
+
+    if (branchKey != null) {
+      builder.setBranch(branchKey);
+    } else if (pullRequestKey != null) {
+      builder.setPullRequest(pullRequestKey);
+    }
+
+    if (lastActivity != null) {
+      List<String> warnings = dbClient.ceTaskMessageDao().selectByTask(dbSession, lastActivity.getUuid()).stream()
+        .map(CeTaskMessageDto::getMessage)
+        .collect(Collectors.toList());
+
+      builder.addAllWarnings(warnings);
+    }
+
+    return builder.build();
+  }
+
+  private String getOrganizationKey(DbSession dbSession, ComponentDto component) {
+    String organizationUuid = component.getOrganizationUuid();
+    return dbClient.organizationDao().selectByUuid(dbSession, organizationUuid)
+      .orElseThrow(() -> new IllegalStateException("Unknown organization: " + organizationUuid))
+      .getKey();
+  }
+
+}
index ac415b72a751c97936bc8d7ae9ca1357f33eccdf..5aead082768e042c6d24215623c334c981b8ceb0 100644 (file)
@@ -28,6 +28,7 @@ public class CeWsModule extends Module {
       CeWs.class,
       ActivityAction.class,
       ActivityStatusAction.class,
+      AnalysisStatusAction.class,
       CancelAction.class,
       CancelAllAction.class,
       ComponentAction.class,
index fdfb881f88b972007b41aa6003f423f4093a1cf6..5ceb1c8cdc3fa2ca9f55b5111486d1daaae17b77 100644 (file)
@@ -33,6 +33,9 @@ public class CeWsParameters {
   public static final String PARAM_MIN_SUBMITTED_AT = "minSubmittedAt";
   public static final String PARAM_MAX_EXECUTED_AT = "maxExecutedAt";
 
+  public static final String PARAM_BRANCH = "branch";
+  public static final String PARAM_PULL_REQUEST = "pullRequest";
+
   private CeWsParameters() {
     // prevent instantiation
   }
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json b/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json
new file mode 100644 (file)
index 0000000..33a06b1
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "component": {
+    "organization": "my-org-1",
+    "key": "com.github.kevinsawicki:http-request-parent",
+    "name": "HttpRequest",
+    "warnings": []
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java
new file mode 100644 (file)
index 0000000..9720443
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskMessageDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Ce;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS;
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_BRANCH;
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT;
+import static org.sonar.server.ce.ws.CeWsParameters.PARAM_PULL_REQUEST;
+import static org.sonar.test.JsonAssert.assertJson;
+
+@RunWith(DataProviderRunner.class)
+public class AnalysisStatusActionTest {
+  private static final String BRANCH_WITH_WARNING = "feature-with-warning";
+  private static final String BRANCH_WITHOUT_WARNING = "feature-without-warning";
+  private static final String PULL_REQUEST = "pr1";
+
+  private static final String WARNING_IN_MAIN = "warning in main";
+  private static final String WARNING_IN_BRANCH = "warning in branch";
+  private static final String WARNING_IN_PR = "warning in pr";
+
+  private static int counter = 1;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone().logIn().setSystemAdministrator();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = db.getDbClient();
+  private WsActionTester ws = new WsActionTester(new AnalysisStatusAction(userSession, dbClient, TestComponentFinder.from(db)));
+
+  @Test
+  public void fail_if_component_key_not_provided() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    ws.newRequest().execute();
+  }
+
+  @Test
+  public void fail_if_component_key_is_unknown() {
+    expectedException.expect(NotFoundException.class);
+
+    ws.newRequest().setParam(PARAM_COMPONENT, "nonexistent").execute();
+  }
+
+  @Test
+  public void fail_if_both_branch_and_pullRequest_are_specified() {
+    expectedException.expect(BadRequestException.class);
+
+    ws.newRequest()
+      .setParam(PARAM_COMPONENT, "dummy")
+      .setParam(PARAM_BRANCH, "feature1")
+      .setParam(PARAM_PULL_REQUEST, "pr1")
+      .execute();
+  }
+
+  @Test
+  @UseDataProvider("nonProjectComponentFactory")
+  public void fail_if_component_is_not_a_project(Function<ComponentDto, ComponentDto> nonProjectComponentFactory) {
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("must be a project");
+
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    ComponentDto component = nonProjectComponentFactory.apply(project);
+    db.components().insertComponent(component);
+
+    ws.newRequest()
+      .setParam(PARAM_COMPONENT, component.getKey())
+      .execute();
+  }
+
+  @DataProvider
+  public static Object[][] nonProjectComponentFactory() {
+    return new Object[][] {
+      {(Function<ComponentDto, ComponentDto>) ComponentTesting::newModuleDto},
+      {(Function<ComponentDto, ComponentDto>) p -> ComponentTesting.newDirectory(p, "foo")},
+      {(Function<ComponentDto, ComponentDto>) ComponentTesting::newFileDto}
+    };
+  }
+
+  @Test
+  public void json_example() {
+    OrganizationDto organization = db.organizations().insert(o -> o.setKey("my-org-1"));
+    ComponentDto project = db.components().insertPrivateProject(organization,
+      p -> p.setUuid("AU_w74XMgAS1Hm6h4-Y-"),
+      p -> p.setDbKey("com.github.kevinsawicki:http-request-parent"),
+      p -> p.setName("HttpRequest"));
+
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    String result = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .execute()
+      .getInput();
+
+    assertJson(result).isSimilarTo(getClass().getResource("analysis_status-example.json"));
+  }
+
+  @Test
+  public void no_errors_no_warnings() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    Ce.AnalysisStatusWsResponse response = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response.getComponent().getWarningsList()).isEmpty();
+  }
+
+  @Test
+  public void return_warnings_for_last_analysis_of_main() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    SnapshotDto analysis = db.components().insertSnapshot(project);
+    CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis);
+    createTaskMessage(activity, WARNING_IN_MAIN);
+
+    Ce.AnalysisStatusWsResponse response = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN);
+
+    SnapshotDto analysis2 = db.components().insertSnapshot(project);
+    insertActivity("task-uuid" + counter++, project, SUCCESS, analysis2);
+
+    Ce.AnalysisStatusWsResponse response2 = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response2.getComponent().getWarningsList()).isEmpty();
+  }
+
+  @Test
+  public void return_warnings_for_last_analysis_of_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING));
+    SnapshotDto analysis = db.components().insertSnapshot(branch);
+    CeActivityDto activity = insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis);
+    createTaskMessage(activity, WARNING_IN_BRANCH);
+
+    Ce.AnalysisStatusWsResponse response = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH);
+
+    SnapshotDto analysis2 = db.components().insertSnapshot(branch);
+    insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis2);
+
+    Ce.AnalysisStatusWsResponse response2 = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response2.getComponent().getWarningsList()).isEmpty();
+  }
+
+  @Test
+  public void return_warnings_for_last_analysis_of_pull_request() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> {
+      b.setBranchType(BranchType.PULL_REQUEST);
+      b.setKey(PULL_REQUEST);
+    });
+    SnapshotDto analysis = db.components().insertSnapshot(pullRequest);
+    CeActivityDto activity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis);
+    createTaskMessage(activity, WARNING_IN_PR);
+
+    Ce.AnalysisStatusWsResponse response = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_PULL_REQUEST, PULL_REQUEST)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR);
+
+    SnapshotDto analysis2 = db.components().insertSnapshot(pullRequest);
+    insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis2);
+
+    Ce.AnalysisStatusWsResponse response2 = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_PULL_REQUEST, PULL_REQUEST)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(response2.getComponent().getWarningsList()).isEmpty();
+  }
+
+  @Test
+  public void return_warnings_per_branch() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    SnapshotDto analysis = db.components().insertSnapshot(project);
+    CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis);
+    createTaskMessage(activity, WARNING_IN_MAIN);
+
+    ComponentDto branchWithWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING));
+    SnapshotDto branchAnalysis = db.components().insertSnapshot(branchWithWarning);
+    CeActivityDto branchActivity = insertActivity("task-uuid" + counter++, branchWithWarning, SUCCESS, branchAnalysis);
+    createTaskMessage(branchActivity, WARNING_IN_BRANCH);
+
+    ComponentDto branchWithoutWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITHOUT_WARNING));
+    SnapshotDto branchWithoutWarningAnalysis = db.components().insertSnapshot(branchWithoutWarning);
+    insertActivity("task-uuid" + counter++, branchWithoutWarning, SUCCESS, branchWithoutWarningAnalysis);
+
+    ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> {
+      b.setBranchType(BranchType.PULL_REQUEST);
+      b.setKey(PULL_REQUEST);
+    });
+    SnapshotDto prAnalysis = db.components().insertSnapshot(pullRequest);
+    CeActivityDto prActivity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, prAnalysis);
+    createTaskMessage(prActivity, WARNING_IN_PR);
+
+    Ce.AnalysisStatusWsResponse responseForMain = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForMain.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN);
+
+    Ce.AnalysisStatusWsResponse responseForBranchWithWarning = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForBranchWithWarning.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH);
+
+    Ce.AnalysisStatusWsResponse responseForBranchWithoutWarning = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_BRANCH, BRANCH_WITHOUT_WARNING)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForBranchWithoutWarning.getComponent().getWarningsList()).isEmpty();
+
+    Ce.AnalysisStatusWsResponse responseForPr = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_PULL_REQUEST, PULL_REQUEST)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForPr.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR);
+  }
+
+  @Test
+  public void response_contains_branch_or_pullRequest_for_branch_or_pullRequest_only() {
+    ComponentDto project = db.components().insertPrivateProject();
+    userSession.addProjectPermission(UserRole.USER, project);
+
+    db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITHOUT_WARNING));
+
+    db.components().insertProjectBranch(project, b -> {
+      b.setBranchType(BranchType.PULL_REQUEST);
+      b.setKey(PULL_REQUEST);
+    });
+
+    Ce.AnalysisStatusWsResponse responseForMain = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForMain.getComponent().hasBranch()).isFalse();
+    assertThat(responseForMain.getComponent().hasPullRequest()).isFalse();
+
+    Ce.AnalysisStatusWsResponse responseForBranchWithoutWarning = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_BRANCH, BRANCH_WITHOUT_WARNING)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForBranchWithoutWarning.getComponent().getBranch()).isEqualTo(BRANCH_WITHOUT_WARNING);
+    assertThat(responseForBranchWithoutWarning.getComponent().hasPullRequest()).isFalse();
+
+    Ce.AnalysisStatusWsResponse responseForPr = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_PULL_REQUEST, PULL_REQUEST)
+      .executeProtobuf(Ce.AnalysisStatusWsResponse.class);
+
+    assertThat(responseForPr.getComponent().hasBranch()).isFalse();
+    assertThat(responseForPr.getComponent().getPullRequest()).isEqualTo(PULL_REQUEST);
+  }
+
+  private void createTaskMessage(CeActivityDto activity, String warning) {
+    db.getDbClient().ceTaskMessageDao().insert(db.getSession(), new CeTaskMessageDto()
+      .setUuid("m-uuid-" + counter++)
+      .setTaskUuid(activity.getUuid())
+      .setMessage(warning)
+      .setCreatedAt(counter));
+    db.commit();
+  }
+
+  private CeActivityDto insertActivity(String taskUuid, ComponentDto component, CeActivityDto.Status status, @Nullable SnapshotDto analysis) {
+    CeQueueDto queueDto = new CeQueueDto();
+    queueDto.setTaskType(CeTaskTypes.REPORT);
+    queueDto.setComponent(component);
+    queueDto.setUuid(taskUuid);
+    CeActivityDto activityDto = new CeActivityDto(queueDto);
+    activityDto.setStatus(status);
+    activityDto.setExecutionTimeMs(500L);
+    activityDto.setAnalysisUuid(analysis == null ? null : analysis.getUuid());
+    activityDto.setExecutedAt((long) counter++);
+    db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto);
+    db.getSession().commit();
+    return activityDto;
+  }
+}
index a16e0b554fae04a8fdf6c0dacff01702350a7080..81bcddb6a3cc89897df6201837e77046d5daad0d 100644 (file)
@@ -31,6 +31,6 @@ public class CeWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new CeWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(15 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
+    assertThat(container.size()).isEqualTo(16 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
   }
 }
index 3216ab435e0001c8879cb18aedda4d17a254d5c9..0443f65eed097a179506aeab9adccee3a5ba0304 100644 (file)
@@ -51,6 +51,20 @@ message ActivityStatusWsResponse {
   optional int32 inProgress = 3;
 }
 
+// GET api/ce/analysis_status
+message AnalysisStatusWsResponse {
+  optional Component component = 1;
+
+  message Component {
+    optional string organization = 1;
+    optional string key = 2;
+    optional string name = 3;
+    repeated string warnings = 4;
+    optional string branch = 5;
+    optional string pullRequest = 6;
+  }
+}
+
 // GET api/ce/component
 message ComponentResponse {
   repeated Task queue = 1;