]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9616 Create api/projectbranches/show
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 11 Aug 2017 17:37:42 +0000 (19:37 +0200)
committerJanos Gyerik <janos.gyerik@sonarsource.com>
Tue, 12 Sep 2017 08:59:56 +0000 (10:59 +0200)
16 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ListAction.java
server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ShowAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/projectbranch/ws/show-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ShowActionTest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java
sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java
sonar-ws/src/main/protobuf/ws-projectbranches.proto
sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java

index 0d169e35d0a283d1f703497c3a74f42c4d34e115..50ff5f66debae87124f502fd079a7600b9cddea9 100644 (file)
@@ -66,6 +66,11 @@ public class BranchDao implements Dao {
     return executeLargeInputs(uuids, mapper(session)::selectByUuids);
   }
 
+  public Optional<BranchDto> selectByUuid(DbSession session, String uuid) {
+    return Optional.ofNullable(mapper(session).selectByUuid(uuid));
+  }
+
+
   private static BranchMapper mapper(DbSession dbSession) {
     return dbSession.getMapper(BranchMapper.class);
   }
index 2e4df028bc1d0f1a6c87a12d9e44ea3a77f4e31e..0c978360aec069bde4807fd8ac3d0e59082d2f39 100644 (file)
@@ -32,6 +32,8 @@ public interface BranchMapper {
   BranchDto selectByKey(@Param("projectUuid") String projectUuid,
     @Param("keyType") BranchKeyType keyType, @Param("key") String key);
 
+  BranchDto selectByUuid(@Param("uuid") String uuid);
+
   Collection<BranchDto> selectByProjectUuid(@Param("projectUuid") String projectUuid);
 
   List<BranchDto> selectByUuids(@Param("uuids") Collection<String> uuids);
index 78215959491105a3e0ab05e882fc5354a7c62210..9c39fff0ac14e3e3beca240b969c412e4dfd8576 100644 (file)
     </foreach>
   </select>
 
+  <select id="selectByUuid" resultType="org.sonar.db.component.BranchDto">
+    select <include refid="columns" />
+    from project_branches pb
+    where
+    pb.uuid = #{uuid, jdbcType=VARCHAR}
+  </select>
+
 </mapper>
index 86dc1db2594191eb2e948a049a445c2f96f82a6e..616a6e1cde115cbd2410fbd06a093b1969a4534c 100644 (file)
@@ -189,4 +189,17 @@ public class BranchDaoTest {
       .containsExactlyInAnyOrder(branch1.uuid());
     assertThat(underTest.selectByUuids(db.getSession(), singletonList("unknown"))).isEmpty();
   }
+
+  @Test
+  public void selectByUuid() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch1 = db.components().insertProjectBranch(project);
+    ComponentDto branch2 = db.components().insertProjectBranch(project);
+
+    assertThat(underTest.selectByUuid(db.getSession(), branch1.uuid()).get())
+      .extracting(BranchDto::getUuid)
+      .containsExactlyInAnyOrder(branch1.uuid());
+    assertThat(underTest.selectByUuid(db.getSession(), project.uuid())).isNotPresent();
+    assertThat(underTest.selectByUuid(db.getSession(), "unknown")).isNotPresent();
+  }
 }
index ec479bf2e2ee3900271bbfdacf5fb07b7893930d..dc2f394bb112ec12f0761794aad4bfab2a9853e0 100644 (file)
@@ -105,6 +105,14 @@ public class ComponentFinder {
     throw new NotFoundException(format(message, messageArguments));
   }
 
+  private static ComponentDto checkComponent(java.util.Optional<ComponentDto> componentDto, String message, Object... messageArguments) {
+    if (componentDto.isPresent() && componentDto.get().isEnabled()) {
+      return componentDto.get();
+    }
+    throw new NotFoundException(format(message, messageArguments));
+  }
+
+
   public ComponentDto getRootComponentByUuidOrKey(DbSession dbSession, @Nullable String projectUuid, @Nullable String projectKey) {
     ComponentDto project;
     if (projectUuid != null) {
@@ -144,7 +152,7 @@ public class ComponentFinder {
   }
 
   public ComponentDto getByKeyAndBranch(DbSession dbSession, String key, String branch) {
-    return checkFoundWithOptional(dbClient.componentDao().selectByKeyAndBranch(dbSession, key, branch), "Component '%s' on branch '%s' not found", key, branch);
+    return checkComponent(dbClient.componentDao().selectByKeyAndBranch(dbSession, key, branch), "Component '%s' on branch '%s' not found", key, branch);
   }
 
   public enum ParamNames {
index cfee79582ef3e4e78c4977613575da74ae28a5df..0a0d4c2cd80fee67537e7da1f25a9cea4ad43780 100644 (file)
@@ -26,6 +26,7 @@ public class BranchWsModule extends Module {
   protected void configureModule() {
     add(
       ListAction.class,
+      ShowAction.class,
       BranchesWs.class);
   }
 }
index 4b1d5783cc4387f4dc1f735da01c4da405e08392..211799777a0e3da9dede941e4afb294ecc01de48 100644 (file)
@@ -25,7 +25,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -42,12 +41,14 @@ import org.sonar.server.ws.WsUtils;
 import org.sonarqube.ws.WsBranches;
 import org.sonarqube.ws.WsBranches.Branch.Status;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Arrays.asList;
 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
 import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
 import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
 import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.core.util.stream.MoreCollectors.index;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
@@ -74,6 +75,7 @@ public class ListAction implements BranchWsAction {
       .setSince("6.6")
       .setDescription("List the branches of a project")
       .setResponseExample(Resources.getResource(getClass(), "list-example.json"))
+      .setInternal(true)
       .setHandler(this);
 
     action
@@ -93,9 +95,7 @@ public class ListAction implements BranchWsAction {
         "Project key '%s' not found", projectKey);
 
       userSession.checkComponentPermission(UserRole.USER, project);
-      if (!project.isEnabled() || !Qualifiers.PROJECT.equals(project.qualifier())) {
-        throw new IllegalArgumentException("Invalid project key");
-      }
+      checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key");
 
       List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, asList(ALERT_STATUS_KEY, BUGS_KEY, VULNERABILITIES_KEY, CODE_SMELLS_KEY));
       Map<Integer, MetricDto> metricsById = metrics.stream().collect(uniqueIndex(MetricDto::getId));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ShowAction.java
new file mode 100644 (file)
index 0000000..0129706
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.projectbranch.ws;
+
+import com.google.common.collect.Multimap;
+import com.google.common.io.Resources;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.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.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.WsUtils;
+import org.sonarqube.ws.WsBranches;
+import org.sonarqube.ws.WsBranches.Branch.Status;
+import org.sonarqube.ws.WsBranches.ShowWsResponse;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Arrays.asList;
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
+import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
+import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.db.component.BranchType.LONG;
+import static org.sonar.db.component.BranchType.SHORT;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_FILE_EXAMPLE_001;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_SHOW;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_COMPONENT;
+
+public class ShowAction implements BranchWsAction {
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final ComponentFinder componentFinder;
+
+  public ShowAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+    this.componentFinder = componentFinder;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction(ACTION_SHOW)
+      .setSince("6.6")
+      .setDescription("Show branch information of a project")
+      .setResponseExample(Resources.getResource(getClass(), "show-example.json"))
+      .setInternal(true)
+      .setHandler(this);
+
+    action
+      .createParam(PARAM_COMPONENT)
+      .setDescription("Component key")
+      .setExampleValue(KEY_FILE_EXAMPLE_001)
+      .setRequired(true);
+
+    action
+      .createParam(PARAM_BRANCH)
+      .setDescription("Branch key")
+      .setExampleValue(KEY_BRANCH_EXAMPLE_001)
+      .setRequired(true);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String projectKey = request.mandatoryParam(PARAM_COMPONENT);
+    String branchName = request.mandatoryParam(PARAM_BRANCH);
+
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      ComponentDto component = componentFinder.getByKeyAndBranch(dbSession, projectKey, branchName);
+      userSession.checkComponentPermission(UserRole.USER, component);
+      ComponentDto project = componentFinder.getByUuid(dbSession, component.projectUuid());
+
+      List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, asList(ALERT_STATUS_KEY, BUGS_KEY, VULNERABILITIES_KEY, CODE_SMELLS_KEY));
+      Map<Integer, MetricDto> metricsById = metrics.stream().collect(uniqueIndex(MetricDto::getId));
+      Map<String, Integer> metricIdsByKey = metrics.stream().collect(uniqueIndex(MetricDto::getKey, MetricDto::getId));
+
+      BranchDto branch = getBranch(dbSession, component.projectUuid());
+      String mergeBranchUuid = branch.getMergeBranchUuid();
+      BranchDto mergeBranch = mergeBranchUuid == null ? null : getBranch(dbSession, mergeBranchUuid);
+
+      Multimap<String, MeasureDto> measuresByComponentUuids = dbClient.measureDao()
+        .selectByComponentsAndMetrics(dbSession, Collections.singletonList(branch.getUuid()), metricsById.keySet())
+        .stream().collect(index(MeasureDto::getComponentUuid));
+
+      WsUtils.writeProtobuf(buildResponse(branch, project, mergeBranch, metricIdsByKey, measuresByComponentUuids), request, response);
+    }
+  }
+
+  private BranchDto getBranch(DbSession dbSession, String uuid) {
+    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbSession, uuid);
+    checkState(branch != null, "Branch uuid '%s' not found", uuid);
+    return branch.get();
+  }
+
+  private static ShowWsResponse buildResponse(BranchDto branch, ComponentDto project, @Nullable BranchDto mergeBranch,
+    Map<String, Integer> metricIdsByKey, Multimap<String, MeasureDto> measuresByComponentUuids) {
+    WsBranches.Branch.Builder builder = WsBranches.Branch.newBuilder();
+    setNullable(branch.getKey(), builder::setName);
+    builder.setProject(project.getKey());
+    builder.setIsMain(branch.isMain());
+    builder.setType(WsBranches.Branch.BranchType.valueOf(branch.getBranchType().name()));
+    if (mergeBranch != null) {
+      setNullable(mergeBranch.getKey(), builder::setMergeBranch);
+    }
+
+    Status.Builder statusBuilder = Status.newBuilder();
+    Collection<MeasureDto> componentMeasures = measuresByComponentUuids.get(branch.getUuid());
+    if (branch.getBranchType().equals(LONG)) {
+      int qualityGateStatusMetricId = metricIdsByKey.get(ALERT_STATUS_KEY);
+      componentMeasures.stream().filter(m -> m.getMetricId() == qualityGateStatusMetricId).findAny()
+        .ifPresent(measure -> statusBuilder.setQualityGateStatus(measure.getData()));
+    }
+
+    if (branch.getBranchType().equals(SHORT)) {
+      int bugsMetricId = metricIdsByKey.get(BUGS_KEY);
+      componentMeasures.stream().filter(m -> m.getMetricId() == bugsMetricId).findAny()
+        .ifPresent(measure -> statusBuilder.setBugs(measure.getValue().intValue()));
+
+      int vulnerabilitiesMetricId = metricIdsByKey.get(VULNERABILITIES_KEY);
+      componentMeasures.stream().filter(m -> m.getMetricId() == vulnerabilitiesMetricId).findAny()
+        .ifPresent(measure -> statusBuilder.setVulnerabilities(measure.getValue().intValue()));
+
+      int codeSmellMetricId = metricIdsByKey.get(CODE_SMELLS_KEY);
+      componentMeasures.stream().filter(m -> m.getMetricId() == codeSmellMetricId).findAny()
+        .ifPresent(measure -> statusBuilder.setCodeSmells(measure.getValue().intValue()));
+    }
+
+    builder.setStatus(statusBuilder);
+    return ShowWsResponse.newBuilder().setBranch(builder).build();
+  }
+
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/projectbranch/ws/show-example.json b/server/sonar-server/src/main/resources/org/sonar/server/projectbranch/ws/show-example.json
new file mode 100644 (file)
index 0000000..857822f
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "branch": {
+    "name": "feature/bar",
+    "project": "sonarqube",
+    "isMain": false,
+    "type": "LONG"
+  }
+}
index 35d8690dd293ca49f69033c90bfc0b9e567602ec..39810004936f417ed0e07405562176c00354b059 100644 (file)
@@ -30,6 +30,6 @@ public class BranchWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new BranchWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
   }
 }
index df17348fee65ea13e57e712f59daf65cd704c38e..d6fc838000b582ea4526464cf446594306dc42eb 100644 (file)
@@ -80,6 +80,7 @@ public class ListActionTest {
     WebService.Action definition = tester.getDef();
     assertThat(definition.key()).isEqualTo("list");
     assertThat(definition.isPost()).isFalse();
+    assertThat(definition.isInternal()).isTrue();
     assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project");
     assertThat(definition.since()).isEqualTo("6.6");
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ShowActionTest.java
new file mode 100644 (file)
index 0000000..e37116f
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.projectbranch.ws;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ResourceTypesRule;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.WsBranches.ShowWsResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
+import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
+import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.WsBranches.Branch;
+
+public class ShowActionTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private ResourceTypesRule resourceTypes = new ResourceTypesRule()
+    .setRootQualifiers(PROJECT);
+
+  private MetricDto qualityGateStatus;
+  private MetricDto bugs;
+  private MetricDto vulnerabilities;
+  private MetricDto codeSmells;
+
+  public WsActionTester ws = new WsActionTester(new ShowAction(db.getDbClient(), userSession, new ComponentFinder(db.getDbClient(), resourceTypes)));
+
+  @Before
+  public void setUp() throws Exception {
+    qualityGateStatus = db.measures().insertMetric(m -> m.setKey(ALERT_STATUS_KEY));
+    bugs = db.measures().insertMetric(m -> m.setKey(BUGS_KEY));
+    vulnerabilities = db.measures().insertMetric(m -> m.setKey(VULNERABILITIES_KEY));
+    codeSmells = db.measures().insertMetric(m -> m.setKey(CODE_SMELLS_KEY));
+  }
+
+  @Test
+  public void test_definition() {
+    WebService.Action definition = ws.getDef();
+    assertThat(definition.key()).isEqualTo("show");
+    assertThat(definition.isPost()).isFalse();
+    assertThat(definition.isInternal()).isTrue();
+    assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("component", "branch");
+    assertThat(definition.since()).isEqualTo("6.6");
+  }
+
+  @Test
+  public void long_living_branch() {
+    ComponentDto project = db.components().insertMainBranch();
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project,
+      b -> b.setKey("long").setBranchType(BranchType.LONG));
+
+    ShowWsResponse response = ws.newRequest()
+      .setParam("component", longLivingBranch.getKey())
+      .setParam("branch", longLivingBranch.getBranch())
+      .executeProtobuf(ShowWsResponse.class);
+
+    assertThat(response.getBranch())
+      .extracting(Branch::getName, Branch::getProject, Branch::getType, Branch::getMergeBranch)
+      .containsExactlyInAnyOrder(longLivingBranch.getBranch(), project.getKey(), Branch.BranchType.LONG, "");
+  }
+
+  @Test
+  public void short_living_branches() {
+    ComponentDto project = db.components().insertMainBranch();
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project,
+      b -> b.setKey("long").setBranchType(BranchType.LONG));
+    ComponentDto shortLivingBranch = db.components().insertProjectBranch(project,
+      b -> b.setKey("short").setBranchType(BranchType.SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
+
+    ShowWsResponse response = ws.newRequest()
+      .setParam("component", shortLivingBranch.getKey())
+      .setParam("branch", shortLivingBranch.getBranch())
+      .executeProtobuf(ShowWsResponse.class);
+
+    assertThat(response.getBranch())
+      .extracting(Branch::getName, Branch::getProject, Branch::getType, Branch::getMergeBranch)
+      .containsExactlyInAnyOrder(shortLivingBranch.getBranch(), project.getKey(), Branch.BranchType.SHORT, longLivingBranch.getBranch());
+  }
+
+  @Test
+  public void quality_gate_status_on_long_living_branch() {
+    ComponentDto project = db.components().insertMainBranch();
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+    SnapshotDto branchAnalysis = db.components().insertSnapshot(branch);
+    db.measures().insertMeasure(branch, branchAnalysis, qualityGateStatus, m -> m.setData("OK"));
+
+    ShowWsResponse response = ws.newRequest()
+      .setParam("component", branch.getKey())
+      .setParam("branch", branch.getBranch())
+      .executeProtobuf(ShowWsResponse.class);
+
+    assertThat(response.getBranch())
+      .extracting(b -> b.getStatus().hasQualityGateStatus(), b -> b.getStatus().getQualityGateStatus())
+      .containsExactlyInAnyOrder(true, "OK");
+  }
+
+  @Test
+  public void bugs_vulnerabilities_and_code_smells_on_short_living_branch() {
+    ComponentDto project = db.components().insertMainBranch();
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+    ComponentDto shortLivingBranch = db.components().insertProjectBranch(project,
+      b -> b.setBranchType(BranchType.SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
+    SnapshotDto branchAnalysis = db.components().insertSnapshot(shortLivingBranch);
+    db.measures().insertMeasure(shortLivingBranch, branchAnalysis, bugs, m -> m.setValue(1d));
+    db.measures().insertMeasure(shortLivingBranch, branchAnalysis, vulnerabilities, m -> m.setValue(2d));
+    db.measures().insertMeasure(shortLivingBranch, branchAnalysis, codeSmells, m -> m.setValue(3d));
+
+    ShowWsResponse response = ws.newRequest()
+      .setParam("component", shortLivingBranch.getKey())
+      .setParam("branch", shortLivingBranch.getBranch())
+      .executeProtobuf(ShowWsResponse.class);
+
+
+    assertThat(response.getBranch().getStatus())
+      .extracting(Branch.Status::hasBugs, Branch.Status::getBugs, Branch.Status::hasVulnerabilities, Branch.Status::getVulnerabilities, Branch.Status::hasCodeSmells, Branch.Status::getCodeSmells)
+      .containsExactlyInAnyOrder(true, 1, true, 2, true, 3);
+  }
+
+  @Test
+  public void file() {
+    ComponentDto project = db.components().insertMainBranch();
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+    ComponentDto file = db.components().insertComponent(newFileDto(longLivingBranch));
+
+    ShowWsResponse response = ws.newRequest()
+      .setParam("component", file.getKey())
+      .setParam("branch", file.getBranch())
+      .executeProtobuf(ShowWsResponse.class);
+
+    assertThat(response.getBranch())
+      .extracting(Branch::getName, Branch::getProject, Branch::getType, Branch::getMergeBranch)
+      .containsExactlyInAnyOrder(file.getBranch(), project.getKey(), Branch.BranchType.LONG, "");
+  }
+
+  @Test
+  public void fail_if_missing_component_parameter() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'component' parameter is missing");
+
+    ws.newRequest()
+      .setParam("branch", "my_branch")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_missing_branch_parameter() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'branch' parameter is missing");
+
+    ws.newRequest()
+      .setParam("component", "my_project")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_branch_does_not_exist() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    userSession.addProjectPermission(UserRole.USER, project);
+    db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage(String.format("Component '%s' on branch '%s' not found", file.getKey(), "another_branch"));
+
+    ws.newRequest()
+      .setParam("component", file.getKey())
+      .setParam("branch", "another_branch")
+      .execute();
+  }
+
+  @Test
+  public void test_example() {
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("sonarqube"));
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setKey("feature/bar").setBranchType(BranchType.LONG));
+    db.components().insertProjectBranch(project, b -> b.setKey("feature/foo").setBranchType(BranchType.SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
+    userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+    String json = ws.newRequest()
+      .setParam("component", longLivingBranch.getKey())
+      .setParam("branch", longLivingBranch.getBranch())
+      .execute()
+      .getInput();
+
+    assertJson(json).isSimilarTo(ws.getDef().responseExampleAsString());
+  }
+
+}
index 9b056ef0bc787da468becd8498ea4b61e4bca5f4..e64477d1b11b8ffde2da51e5d550635800c35d5c 100644 (file)
@@ -25,9 +25,12 @@ public class ProjectBranchesParameters {
 
   // actions
   public static final String ACTION_LIST = "list";
+  public static final String ACTION_SHOW = "show";
 
   // parameters
   public static final String PARAM_PROJECT = "project";
+  public static final String PARAM_COMPONENT = "component";
+  public static final String PARAM_BRANCH = "branch";
 
   private ProjectBranchesParameters() {
     // static utility class
index bb0aecba3d55c7f21ffe4959e5b2e378652202a5..c9a5a21fc240e6fbdbfded94e69cdaab20ffae68 100644 (file)
 package org.sonarqube.ws.client.projectbranches;
 
 import org.sonarqube.ws.WsBranches.ListWsResponse;
+import org.sonarqube.ws.WsBranches.ShowWsResponse;
 import org.sonarqube.ws.client.BaseService;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.WsConnector;
 
 import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_LIST;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.ACTION_SHOW;
 import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.CONTROLLER;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH;
 import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT;
 
 public class ProjectBranchesService extends BaseService {
@@ -40,4 +43,11 @@ public class ProjectBranchesService extends BaseService {
     return call(get, ListWsResponse.parser());
   }
 
+  public ShowWsResponse show(String project, String branch) {
+    GetRequest get = new GetRequest(path(ACTION_SHOW))
+      .setParam(PARAM_PROJECT, project)
+      .setParam(PARAM_BRANCH, branch);
+    return call(get, ShowWsResponse.parser());
+  }
+
 }
index 915e983e788e61c9e005dce79a86975afe3ce40a..8054febfafc4614f692821de9c9e0d762cd599bc 100644 (file)
@@ -30,6 +30,11 @@ message ListWsResponse {
   repeated Branch branches = 1;
 }
 
+// WS api/project_branches/show
+message ShowWsResponse {
+  optional Branch branch = 1;
+}
+
 message Branch {
   optional string name = 1;
   optional string project = 2;
index c3dbc7c581b1652a3544b1776f91cc4ec6b6613f..54bec8cd894255d009bebd9ceb39cc02eb55028a 100644 (file)
@@ -23,12 +23,14 @@ package org.sonarqube.ws.client.projectbranches;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonarqube.ws.WsBranches.ListWsResponse;
+import org.sonarqube.ws.WsBranches.ShowWsResponse;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.ServiceTester;
 import org.sonarqube.ws.client.WsConnector;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_BRANCH;
 import static org.sonarqube.ws.client.projectbranches.ProjectBranchesParameters.PARAM_PROJECT;
 
 public class ProjectBranchesServiceTest {
@@ -51,4 +53,18 @@ public class ProjectBranchesServiceTest {
       .andNoOtherParam();
   }
 
+  @Test
+  public void show() {
+    underTest.show("projectKey", "my_branch");
+
+    assertThat(serviceTester.getGetParser()).isSameAs(ShowWsResponse.parser());
+
+    GetRequest getRequest = serviceTester.getGetRequest();
+    serviceTester.assertThat(getRequest)
+      .hasPath("show")
+      .hasParam(PARAM_PROJECT, "projectKey")
+      .hasParam(PARAM_BRANCH, "my_branch")
+      .andNoOtherParam();
+  }
+
 }