From 50328a09b14418b47232bb75665040fc755d6b58 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 11 Aug 2017 19:37:42 +0200 Subject: [PATCH] SONAR-9616 Create api/projectbranches/show --- .../org/sonar/db/component/BranchDao.java | 5 + .../org/sonar/db/component/BranchMapper.java | 2 + .../org/sonar/db/component/BranchMapper.xml | 7 + .../org/sonar/db/component/BranchDaoTest.java | 13 + .../server/component/ComponentFinder.java | 10 +- .../projectbranch/ws/BranchWsModule.java | 1 + .../server/projectbranch/ws/ListAction.java | 8 +- .../server/projectbranch/ws/ShowAction.java | 167 ++++++++++++ .../server/projectbranch/ws/show-example.json | 8 + .../projectbranch/ws/BranchWsModuleTest.java | 2 +- .../projectbranch/ws/ListActionTest.java | 1 + .../projectbranch/ws/ShowActionTest.java | 237 ++++++++++++++++++ .../ProjectBranchesParameters.java | 3 + .../ProjectBranchesService.java | 10 + .../main/protobuf/ws-projectbranches.proto | 5 + .../ProjectBranchesServiceTest.java | 16 ++ 16 files changed, 489 insertions(+), 6 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ShowAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/projectbranch/ws/show-example.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ShowActionTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java index 0d169e35d0a..50ff5f66deb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java @@ -66,6 +66,11 @@ public class BranchDao implements Dao { return executeLargeInputs(uuids, mapper(session)::selectByUuids); } + public Optional selectByUuid(DbSession session, String uuid) { + return Optional.ofNullable(mapper(session).selectByUuid(uuid)); + } + + private static BranchMapper mapper(DbSession dbSession) { return dbSession.getMapper(BranchMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java index 2e4df028bc1..0c978360aec 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java @@ -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 selectByProjectUuid(@Param("projectUuid") String projectUuid); List selectByUuids(@Param("uuids") Collection uuids); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml index 78215959491..9c39fff0ac1 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml @@ -72,4 +72,11 @@ + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java index 86dc1db2594..616a6e1cde1 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java @@ -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(); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java index ec479bf2e2e..dc2f394bb11 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java @@ -105,6 +105,14 @@ public class ComponentFinder { throw new NotFoundException(format(message, messageArguments)); } + private static ComponentDto checkComponent(java.util.Optional 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 { diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java index cfee79582ef..0a0d4c2cd80 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/BranchWsModule.java @@ -26,6 +26,7 @@ public class BranchWsModule extends Module { protected void configureModule() { add( ListAction.class, + ShowAction.class, BranchesWs.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ListAction.java index 4b1d5783cc4..211799777a0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ListAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ListAction.java @@ -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 metrics = dbClient.metricDao().selectByKeys(dbSession, asList(ALERT_STATUS_KEY, BUGS_KEY, VULNERABILITIES_KEY, CODE_SMELLS_KEY)); Map 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 index 00000000000..0129706d379 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectbranch/ws/ShowAction.java @@ -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 metrics = dbClient.metricDao().selectByKeys(dbSession, asList(ALERT_STATUS_KEY, BUGS_KEY, VULNERABILITIES_KEY, CODE_SMELLS_KEY)); + Map metricsById = metrics.stream().collect(uniqueIndex(MetricDto::getId)); + Map 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 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 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 metricIdsByKey, Multimap 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 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 index 00000000000..857822f4b6f --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/projectbranch/ws/show-example.json @@ -0,0 +1,8 @@ +{ + "branch": { + "name": "feature/bar", + "project": "sonarqube", + "isMain": false, + "type": "LONG" + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java index 35d8690dd29..39810004936 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/BranchWsModuleTest.java @@ -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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ListActionTest.java index df17348fee6..d6fc838000b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ListActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ListActionTest.java @@ -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 index 00000000000..e37116f1fcf --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/projectbranch/ws/ShowActionTest.java @@ -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()); + } + +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java index 9b056ef0bc7..e64477d1b11 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesParameters.java @@ -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 diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java index bb0aecba3d5..c9a5a21fc24 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesService.java @@ -20,12 +20,15 @@ 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()); + } + } diff --git a/sonar-ws/src/main/protobuf/ws-projectbranches.proto b/sonar-ws/src/main/protobuf/ws-projectbranches.proto index 915e983e788..8054febfafc 100644 --- a/sonar-ws/src/main/protobuf/ws-projectbranches.proto +++ b/sonar-ws/src/main/protobuf/ws-projectbranches.proto @@ -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; diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java index c3dbc7c581b..54bec8cd894 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectbranches/ProjectBranchesServiceTest.java @@ -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(); + } + } -- 2.39.5