diff options
273 files changed, 5341 insertions, 519 deletions
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 64ed2a4a89b..f959d2a3fdc 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -120,7 +120,7 @@ public class ComputeEngineContainerImplTest { + 26 // level 1 + 53 // content of DaoModule + 3 // content of EsModule - + 57 // content of CorePropertyDefinitions + + 59 // content of CorePropertyDefinitions + 1 // StopFlagContainer ); assertThat( diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index e036bd73dda..961ca45a144 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -733,13 +733,15 @@ CREATE TABLE "PROJECT_BRANCHES" ( "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, "PROJECT_UUID" VARCHAR(50) NOT NULL, "KEE" VARCHAR(255) NOT NULL, - "BRANCH_TYPE" VARCHAR(5), + "KEY_TYPE" VARCHAR(12) NOT NULL, + "BRANCH_TYPE" VARCHAR(12), "MERGE_BRANCH_UUID" VARCHAR(50), + "PULL_REQUEST_BINARY" BLOB, "CREATED_AT" BIGINT NOT NULL, "UPDATED_AT" BIGINT NOT NULL ); CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); -CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE", "KEY_TYPE"); CREATE TABLE "ANALYSIS_PROPERTIES" ( "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java index 1e342a28a93..1be8ca7416f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java @@ -23,6 +23,7 @@ public class CeTaskCharacteristicDto { public static final String BRANCH_KEY = "branch"; public static final String BRANCH_TYPE_KEY = "branchType"; + public static final String PULL_REQUEST = "pullRequest"; private String uuid; private String taskUuid; 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 fbbecbc5a7b..834e21bb0cd 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 @@ -37,24 +37,42 @@ public class BranchDao implements Dao { } public void insert(DbSession dbSession, BranchDto dto) { + setKeyType(dto); mapper(dbSession).insert(dto, system2.now()); } public void upsert(DbSession dbSession, BranchDto dto) { BranchMapper mapper = mapper(dbSession); long now = system2.now(); + setKeyType(dto); if (mapper.update(dto, now) == 0) { mapper.insert(dto, now); } } + private static void setKeyType(BranchDto dto) { + if (dto.getBranchType() == BranchType.PULL_REQUEST) { + dto.setKeyType(KeyType.PULL_REQUEST); + } else { + dto.setKeyType(KeyType.BRANCH); + } + } + public int updateMainBranchName(DbSession dbSession, String projectUuid, String newBranchKey) { long now = system2.now(); return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, now); } - public Optional<BranchDto> selectByKey(DbSession dbSession, String projectUuid, String key) { - return Optional.ofNullable(mapper(dbSession).selectByKey(projectUuid, key)); + public Optional<BranchDto> selectByBranchKey(DbSession dbSession, String projectUuid, String key) { + return selectByKey(dbSession, projectUuid, key, KeyType.BRANCH); + } + + public Optional<BranchDto> selectByPullRequestKey(DbSession dbSession, String projectUuid, String key) { + return selectByKey(dbSession, projectUuid, key, KeyType.PULL_REQUEST); + } + + private static Optional<BranchDto> selectByKey(DbSession dbSession, String projectUuid, String key, KeyType keyType) { + return Optional.ofNullable(mapper(dbSession).selectByKey(projectUuid, key, keyType)); } public Collection<BranchDto> selectByComponent(DbSession dbSession, ComponentDto component) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java index 2586d87efc0..a1b11b8d2fb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java @@ -19,8 +19,13 @@ */ package org.sonar.db.component; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Objects; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.db.protobuf.DbProjectBranches; import static com.google.common.base.Preconditions.checkArgument; @@ -49,11 +54,19 @@ public class BranchDto { private String projectUuid; /** - * Name of branch, for example "feature/foo". + * Key that identifies a branch or a pull request. + * For keyType=BRANCH, this is the name of the branch, for example "feature/foo". + * For keyType=PULL_REQUEST, this is the ID of the pull request in some external system, for example 123 in GitHub. */ private String kee; /** + * Key type, as provided by {@link KeyType}. + * Not null. + */ + private KeyType keyType; + + /** * Branch type, as provided by {@link BranchType}. * Not null. */ @@ -69,6 +82,12 @@ public class BranchDto { @Nullable private String mergeBranchUuid; + /** + * Pull Request data, such as branch name, title, url, and provider specific attributes + */ + @Nullable + private byte[] pullRequestBinary; + public String getUuid() { return uuid; } @@ -115,6 +134,11 @@ public class BranchDto { return this; } + BranchDto setKeyType(@Nullable KeyType keyType) { + this.keyType = keyType; + return this; + } + public BranchType getBranchType() { return branchType; } @@ -134,6 +158,37 @@ public class BranchDto { return this; } + public BranchDto setPullRequestData(DbProjectBranches.PullRequestData pullRequestData) { + this.pullRequestBinary = encodePullRequestData(pullRequestData); + return this; + } + + @CheckForNull + public DbProjectBranches.PullRequestData getPullRequestData() { + if (pullRequestBinary == null) { + return null; + } + return decodePullRequestData(pullRequestBinary); + } + + private static byte[] encodePullRequestData(DbProjectBranches.PullRequestData pullRequestData) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + pullRequestData.writeTo(outputStream); + return outputStream.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException("Fail to serialize pull request data", e); + } + } + + private static DbProjectBranches.PullRequestData decodePullRequestData(byte[] pullRequestBinary) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pullRequestBinary)) { + return DbProjectBranches.PullRequestData.parseFrom(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Fail to deserialize pull request data", e); + } + } + @Override public boolean equals(Object o) { if (this == o) { @@ -161,6 +216,7 @@ public class BranchDto { sb.append("uuid='").append(uuid).append('\''); sb.append(", projectUuid='").append(projectUuid).append('\''); sb.append(", kee='").append(kee).append('\''); + sb.append(", keyType=").append(keyType); sb.append(", branchType=").append(branchType); sb.append(", mergeBranchUuid='").append(mergeBranchUuid).append('\''); sb.append('}'); 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 b613ab9f44b..17872a13a0e 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 @@ -31,7 +31,7 @@ public interface BranchMapper { int updateMainBranchName(@Param("projectUuid") String projectUuid, @Param("newBranchName") String newBranchName, @Param("now") long now); - BranchDto selectByKey(@Param("projectUuid") String projectUuid, @Param("key") String key); + BranchDto selectByKey(@Param("projectUuid") String projectUuid, @Param("key") String key, @Param("keyType") KeyType keyType); BranchDto selectByUuid(@Param("uuid") String uuid); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java index 3358caf82cf..d5937b7d97d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java @@ -29,5 +29,10 @@ public enum BranchType { /** * Short-lived branch */ - SHORT + SHORT, + + /** + * Pull request + */ + PULL_REQUEST } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index 24eb17266ce..535d585ad73 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -50,6 +50,7 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs; import static org.sonar.db.DatabaseUtils.executeLargeUpdates; import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER; import static org.sonar.db.component.ComponentDto.generateBranchKey; +import static org.sonar.db.component.ComponentDto.generatePullRequestKey; public class ComponentDao implements Dao { @@ -201,6 +202,12 @@ public class ComponentDao implements Dao { return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, branch)); } + public List<ComponentDto> selectByKeysAndPullRequest(DbSession session, Collection<String> keys, String pullRequestId) { + List<String> dbKeys = keys.stream().map(k -> generatePullRequestKey(k, pullRequestId)).collect(toList()); + List<String> allKeys = Stream.of(keys, dbKeys).flatMap(Collection::stream).collect(toList()); + return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, pullRequestId)); + } + public List<ComponentDto> selectComponentsHavingSameKeyOrderedById(DbSession session, String key) { return mapper(session).selectComponentsHavingSameKeyOrderedById(key); } @@ -247,7 +254,11 @@ public class ComponentDao implements Dao { } public java.util.Optional<ComponentDto> selectByKeyAndBranch(DbSession session, String key, String branch) { - return java.util.Optional.ofNullable(mapper(session).selectByKeyAndBranch(key, generateBranchKey(key, branch), branch)); + return java.util.Optional.ofNullable(mapper(session).selectByKeyAndBranchKey(key, generateBranchKey(key, branch), branch)); + } + + public java.util.Optional<ComponentDto> selectByKeyAndPullRequest(DbSession session, String key, String pullRequestId) { + return java.util.Optional.ofNullable(mapper(session).selectByKeyAndBranchKey(key, generatePullRequestKey(key, pullRequestId), pullRequestId)); } public List<UuidWithProjectUuidDto> selectAllViewsAndSubViews(DbSession session) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java index 75d23fd49e5..ef402d4e48f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java @@ -24,15 +24,16 @@ import com.google.common.base.Splitter; import com.google.common.base.Strings; import java.util.Date; import java.util.List; +import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.sonar.api.resources.Scopes; import org.sonar.db.WildcardPosition; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; +import static org.apache.commons.lang.StringUtils.substringBeforeLast; import static org.sonar.db.DaoDatabaseUtils.buildLikeValue; import static org.sonar.db.component.ComponentValidator.checkComponentKey; import static org.sonar.db.component.ComponentValidator.checkComponentName; @@ -44,8 +45,11 @@ public class ComponentDto { * Separator used to generate the key of the branch */ public static final String BRANCH_KEY_SEPARATOR = ":BRANCH:"; + public static final String PULL_REQUEST_SEPARATOR = ":PULL_REQUEST:"; + private static final Splitter BRANCH_OR_PULL_REQUEST_SPLITTER = Splitter.on(Pattern.compile(BRANCH_KEY_SEPARATOR + "|" + PULL_REQUEST_SEPARATOR)); private static final Splitter BRANCH_KEY_SPLITTER = Splitter.on(BRANCH_KEY_SEPARATOR); + private static final Splitter PULL_REQUEST_SPLITTER = Splitter.on(PULL_REQUEST_SEPARATOR); public static final String UUID_PATH_SEPARATOR = "."; public static final String UUID_PATH_OF_ROOT = UUID_PATH_SEPARATOR; @@ -65,7 +69,7 @@ public class ComponentDto { private String organizationUuid; /** - * Non-empty and unique functional key + * Non-empty and unique functional key. Do not rename, used by MyBatis. */ private String kee; @@ -206,20 +210,6 @@ public class ComponentDto { return UUID_PATH_SPLITTER.splitToList(uuidPath); } - /** - * Used my MyBatis mapper - */ - private String getKee(){ - return kee; - } - - /** - * Used my MyBatis mapper - */ - private void setKee(String kee){ - this.kee = kee; - } - public String getDbKey() { return kee; } @@ -233,7 +223,7 @@ public class ComponentDto { * The key to be displayed to user, doesn't contain information on branches */ public String getKey() { - List<String> split = BRANCH_KEY_SPLITTER.splitToList(kee); + List<String> split = BRANCH_OR_PULL_REQUEST_SPLITTER.splitToList(kee); return split.size() == 2 ? split.get(0) : kee; } @@ -246,6 +236,15 @@ public class ComponentDto { return split.size() == 2 ? split.get(1) : null; } + /** + * @return the pull request id. It will be null when the component is not on a pull request + */ + @CheckForNull + public String getPullRequest() { + List<String> split = PULL_REQUEST_SPLITTER.splitToList(kee); + return split.size() == 2 ? split.get(1) : null; + } + public String scope() { return scope; } @@ -525,8 +524,11 @@ public class ComponentDto { return format("%s%s%s", componentKey, BRANCH_KEY_SEPARATOR, branch); } - public static String removeBranchFromKey(String componentKey) { - return StringUtils.substringBeforeLast(componentKey, ComponentDto.BRANCH_KEY_SEPARATOR); + public static String generatePullRequestKey(String componentKey, String pullRequest) { + return format("%s%s%s", componentKey, PULL_REQUEST_SEPARATOR, pullRequest); } + public static String removeBranchAndPullRequestFromKey(String componentKey) { + return substringBeforeLast(substringBeforeLast(componentKey, ComponentDto.BRANCH_KEY_SEPARATOR), ComponentDto.PULL_REQUEST_SEPARATOR); + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java index 5290e3e2a4a..c3a73736926 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java @@ -141,10 +141,14 @@ public class ComponentKeyUpdaterDao implements Dao { private static String branchBaseKey(String key) { int index = key.lastIndexOf(ComponentDto.BRANCH_KEY_SEPARATOR); - if (index == -1) { - return key; + if (index > -1) { + return key.substring(0, index); } - return key.substring(0, index); + index = key.lastIndexOf(ComponentDto.PULL_REQUEST_SEPARATOR); + if (index > -1) { + return key.substring(0, index); + } + return key; } private static void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java index 5101dc9dd2e..4560af88e6c 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -33,7 +33,7 @@ public interface ComponentMapper { ComponentDto selectByKey(String key); @CheckForNull - ComponentDto selectByKeyAndBranch(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch); + ComponentDto selectByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch); @CheckForNull ComponentDto selectById(long id); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java new file mode 100644 index 00000000000..f8b2767c0ae --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java @@ -0,0 +1,26 @@ +/* + * 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.db.component; + +public enum KeyType { + BRANCH, + + PULL_REQUEST +} diff --git a/server/sonar-db-dao/src/main/protobuf/db-project-branches.proto b/server/sonar-db-dao/src/main/protobuf/db-project-branches.proto new file mode 100644 index 00000000000..ac6f5047f4a --- /dev/null +++ b/server/sonar-db-dao/src/main/protobuf/db-project-branches.proto @@ -0,0 +1,38 @@ +/* + SonarQube, open source software quality management tool. + Copyright (C) 2008-2016 SonarSource + mailto:contact AT sonarsource DOT com + + SonarQube is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + SonarQube is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// Structure of db column PROJECT_BRANCHES.PULL_REQUEST_DATA + +syntax = "proto3"; + +package sonarqube.db.project_branches; + +// The java package can be changed without breaking compatibility. +// it impacts only the generated Java code. +option java_package = "org.sonar.db.protobuf"; +option optimize_for = SPEED; + +message PullRequestData { + string branch = 1; + string title = 2; + string url = 3; + + map<string, string> attributes = 4; +} 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 f14a166566b..caf89177c7d 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 @@ -6,8 +6,10 @@ pb.uuid as uuid, pb.project_uuid as projectUuid, pb.kee as kee, + pb.key_type as keyType, pb.branch_type as branchType, - pb.merge_branch_uuid as mergeBranchUuid + pb.merge_branch_uuid as mergeBranchUuid, + pb.pull_request_binary as pullRequestBinary </sql> <insert id="insert" parameterType="map" useGeneratedKeys="false"> @@ -15,16 +17,20 @@ uuid, project_uuid, kee, + key_type, branch_type, merge_branch_uuid, + pull_request_binary, created_at, updated_at ) values ( #{dto.uuid, jdbcType=VARCHAR}, #{dto.projectUuid, jdbcType=VARCHAR}, #{dto.kee, jdbcType=VARCHAR}, + #{dto.keyType, jdbcType=VARCHAR}, #{dto.branchType, jdbcType=VARCHAR}, #{dto.mergeBranchUuid, jdbcType=VARCHAR}, + #{dto.pullRequestBinary, jdbcType=BINARY}, #{now, jdbcType=BIGINT}, #{now, jdbcType=BIGINT} ) @@ -43,6 +49,7 @@ update project_branches set merge_branch_uuid = #{dto.mergeBranchUuid, jdbcType=VARCHAR}, + pull_request_binary = #{dto.pullRequestBinary, jdbcType=BINARY}, updated_at = #{now, jdbcType=BIGINT} where uuid = #{dto.uuid, jdbcType=VARCHAR} @@ -53,7 +60,8 @@ from project_branches pb where pb.project_uuid = #{projectUuid, jdbcType=VARCHAR} and - pb.kee = #{key, jdbcType=VARCHAR} + pb.kee = #{key, jdbcType=VARCHAR} and + pb.key_type = #{keyType, jdbcType=VARCHAR} </select> <select id="selectByProjectUuid" parameterType="string" resultType="org.sonar.db.component.BranchDto"> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index e415632ecf7..0ba7f68f4e8 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -36,7 +36,7 @@ p.kee=#{key,jdbcType=VARCHAR} </select> - <select id="selectByKeyAndBranch" parameterType="String" resultType="Component"> + <select id="selectByKeyAndBranchKey" parameterType="String" resultType="Component"> SELECT <include refid="componentColumns"/> FROM projects p @@ -666,7 +666,7 @@ ON p.uuid = i.component_uuid JOIN project_branches b ON i.project_uuid = b.uuid - AND b.branch_type = 'SHORT' + AND (b.branch_type = 'SHORT' OR b.branch_type = 'PULL_REQUEST') AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR} AND i.status != 'CLOSED' </select> 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 6a37a316ceb..795b3f20738 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 @@ -26,6 +26,7 @@ import org.sonar.api.utils.System2; import org.sonar.api.utils.internal.TestSystem2; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.protobuf.DbProjectBranches; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -37,7 +38,7 @@ public class BranchDaoTest { private static final long NOW = 1_000L; private static final String SELECT_FROM = "select project_uuid as \"projectUuid\", uuid as \"uuid\", branch_type as \"branchType\", " + - "kee as \"kee\", merge_branch_uuid as \"mergeBranchUuid\", created_at as \"createdAt\", updated_at as \"updatedAt\" " + + "kee as \"kee\", merge_branch_uuid as \"mergeBranchUuid\", pull_request_binary as \"pullRequestBinary\", created_at as \"createdAt\", updated_at as \"updatedAt\" " + "from project_branches "; private System2 system2 = new TestSystem2().setNow(NOW); @@ -64,6 +65,7 @@ public class BranchDaoTest { entry("branchType", "SHORT"), entry("kee", "feature/foo"), entry("mergeBranchUuid", null), + entry("pullRequestBinary", null), entry("createdAt", 1_000L), entry("updatedAt", 1_000L)); } @@ -85,7 +87,7 @@ public class BranchDaoTest { underTest.insert(dbSession, dto2); underTest.updateMainBranchName(dbSession, "U1", "master"); - BranchDto loaded = underTest.selectByKey(dbSession, "U1", "master").get(); + BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "master").get(); assertThat(loaded.getMergeBranchUuid()).isNull(); assertThat(loaded.getProjectUuid()).isEqualTo("U1"); assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); @@ -110,7 +112,76 @@ public class BranchDaoTest { } @Test - public void upsert() { + public void insert_pull_request_branch_with_only_non_null_fields() { + String projectUuid = "U1"; + String uuid = "U2"; + BranchType branchType = BranchType.PULL_REQUEST; + String kee = "123"; + + BranchDto dto = new BranchDto(); + dto.setProjectUuid(projectUuid); + dto.setUuid(uuid); + dto.setBranchType(branchType); + dto.setKey(kee); + + underTest.insert(dbSession, dto); + + BranchDto loaded = underTest.selectByUuid(dbSession, dto.getUuid()).get(); + + assertThat(loaded.getProjectUuid()).isEqualTo(projectUuid); + assertThat(loaded.getUuid()).isEqualTo(uuid); + assertThat(loaded.getBranchType()).isEqualTo(branchType); + assertThat(loaded.getKey()).isEqualTo(kee); + assertThat(loaded.getMergeBranchUuid()).isNull(); + assertThat(loaded.getPullRequestData()).isNull(); + } + + @Test + public void insert_pull_request_branch_with_all_fields() { + String projectUuid = "U1"; + String uuid = "U2"; + BranchType branchType = BranchType.PULL_REQUEST; + String kee = "123"; + + String branch = "feature/pr1"; + String title = "Dummy Feature Title"; + String url = "http://example.com/pullRequests/pr1"; + String tokenAttributeName = "token"; + String tokenAttributeValue = "dummy token"; + DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch) + .setTitle(title) + .setUrl(url) + .putAttributes(tokenAttributeName, tokenAttributeValue) + .build(); + + BranchDto dto = new BranchDto(); + dto.setProjectUuid(projectUuid); + dto.setUuid(uuid); + dto.setBranchType(branchType); + dto.setKey(kee); + dto.setPullRequestData(pullRequestData); + + underTest.insert(dbSession, dto); + + BranchDto loaded = underTest.selectByUuid(dbSession, dto.getUuid()).get(); + + assertThat(loaded.getProjectUuid()).isEqualTo(projectUuid); + assertThat(loaded.getUuid()).isEqualTo(uuid); + assertThat(loaded.getBranchType()).isEqualTo(branchType); + assertThat(loaded.getKey()).isEqualTo(kee); + assertThat(loaded.getMergeBranchUuid()).isNull(); + + DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData(); + assertThat(loadedPullRequestData).isNotNull(); + assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch); + assertThat(loadedPullRequestData.getTitle()).isEqualTo(title); + assertThat(loadedPullRequestData.getUrl()).isEqualTo(url); + assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue); + } + + @Test + public void upsert_branch() { BranchDto dto = new BranchDto(); dto.setProjectUuid("U1"); dto.setUuid("U2"); @@ -126,14 +197,110 @@ public class BranchDaoTest { dto.setBranchType(BranchType.SHORT); underTest.upsert(dbSession, dto); - BranchDto loaded = underTest.selectByKey(dbSession, "U1", "foo").get(); + BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "foo").get(); assertThat(loaded.getMergeBranchUuid()).isEqualTo("U3"); assertThat(loaded.getProjectUuid()).isEqualTo("U1"); assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); } @Test - public void selectByKey() { + public void upsert_pull_request() { + BranchDto dto = new BranchDto(); + dto.setProjectUuid("U1"); + dto.setUuid("U2"); + dto.setBranchType(BranchType.PULL_REQUEST); + dto.setKey("foo"); + underTest.insert(dbSession, dto); + + // the fields that can be updated + dto.setMergeBranchUuid("U3"); + + String branch = "feature/pr1"; + String title = "Dummy Feature Title"; + String url = "http://example.com/pullRequests/pr1"; + String tokenAttributeName = "token"; + String tokenAttributeValue = "dummy token"; + DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch) + .setTitle(title) + .setUrl(url) + .putAttributes(tokenAttributeName, tokenAttributeValue) + .build(); + dto.setPullRequestData(pullRequestData); + + // the fields that can't be updated. New values are ignored. + dto.setProjectUuid("ignored"); + dto.setBranchType(BranchType.SHORT); + underTest.upsert(dbSession, dto); + + BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", "foo").get(); + assertThat(loaded.getMergeBranchUuid()).isEqualTo("U3"); + assertThat(loaded.getProjectUuid()).isEqualTo("U1"); + assertThat(loaded.getBranchType()).isEqualTo(BranchType.PULL_REQUEST); + + DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData(); + assertThat(loadedPullRequestData).isNotNull(); + assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch); + assertThat(loadedPullRequestData.getTitle()).isEqualTo(title); + assertThat(loadedPullRequestData.getUrl()).isEqualTo(url); + assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue); + } + + @Test + public void update_pull_request_data() { + BranchDto dto = new BranchDto(); + dto.setProjectUuid("U1"); + dto.setUuid("U2"); + dto.setBranchType(BranchType.PULL_REQUEST); + dto.setKey("foo"); + + // the fields that can be updated + String mergeBranchUuid = "U3"; + dto.setMergeBranchUuid(mergeBranchUuid + "-dummy-suffix"); + + String branch = "feature/pr1"; + String title = "Dummy Feature Title"; + String url = "http://example.com/pullRequests/pr1"; + String tokenAttributeName = "token"; + String tokenAttributeValue = "dummy token"; + DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch + "-dummy-suffix") + .setTitle(title + "-dummy-suffix") + .setUrl(url + "-dummy-suffix") + .putAttributes(tokenAttributeName, tokenAttributeValue + "-dummy-suffix") + .build(); + dto.setPullRequestData(pullRequestData); + + underTest.insert(dbSession, dto); + + // modify pull request data + + dto.setMergeBranchUuid(mergeBranchUuid); + pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch) + .setTitle(title) + .setUrl(url) + .putAttributes(tokenAttributeName, tokenAttributeValue) + .build(); + dto.setPullRequestData(pullRequestData); + + underTest.upsert(dbSession, dto); + + BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", "foo").get(); + assertThat(loaded.getMergeBranchUuid()).isEqualTo(mergeBranchUuid); + assertThat(loaded.getProjectUuid()).isEqualTo("U1"); + assertThat(loaded.getBranchType()).isEqualTo(BranchType.PULL_REQUEST); + + DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData(); + assertThat(loadedPullRequestData).isNotNull(); + assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch); + assertThat(loadedPullRequestData.getTitle()).isEqualTo(title); + assertThat(loadedPullRequestData.getUrl()).isEqualTo(url); + assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue); + } + + @Test + public void selectByBranchKey() { BranchDto mainBranch = new BranchDto(); mainBranch.setProjectUuid("U1"); mainBranch.setUuid("U1"); @@ -150,7 +317,7 @@ public class BranchDaoTest { underTest.insert(dbSession, featureBranch); // select the feature branch - BranchDto loaded = underTest.selectByKey(dbSession, "U1", "feature/foo").get(); + BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "feature/foo").get(); assertThat(loaded.getUuid()).isEqualTo(featureBranch.getUuid()); assertThat(loaded.getKey()).isEqualTo(featureBranch.getKey()); assertThat(loaded.getProjectUuid()).isEqualTo(featureBranch.getProjectUuid()); @@ -158,7 +325,37 @@ public class BranchDaoTest { assertThat(loaded.getMergeBranchUuid()).isEqualTo(featureBranch.getMergeBranchUuid()); // select a branch on another project with same branch name - assertThat(underTest.selectByKey(dbSession, "U3", "feature/foo")).isEmpty(); + assertThat(underTest.selectByBranchKey(dbSession, "U3", "feature/foo")).isEmpty(); + } + + @Test + public void selectByPullRequestKey() { + BranchDto mainBranch = new BranchDto(); + mainBranch.setProjectUuid("U1"); + mainBranch.setUuid("U1"); + mainBranch.setBranchType(BranchType.LONG); + mainBranch.setKey("master"); + underTest.insert(dbSession, mainBranch); + + String pullRequestId = "123"; + BranchDto pullRequest = new BranchDto(); + pullRequest.setProjectUuid("U1"); + pullRequest.setUuid("U2"); + pullRequest.setBranchType(BranchType.PULL_REQUEST); + pullRequest.setKey(pullRequestId); + pullRequest.setMergeBranchUuid("U3"); + underTest.insert(dbSession, pullRequest); + + // select the feature branch + BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", pullRequestId).get(); + assertThat(loaded.getUuid()).isEqualTo(pullRequest.getUuid()); + assertThat(loaded.getKey()).isEqualTo(pullRequest.getKey()); + assertThat(loaded.getProjectUuid()).isEqualTo(pullRequest.getProjectUuid()); + assertThat(loaded.getBranchType()).isEqualTo(pullRequest.getBranchType()); + assertThat(loaded.getMergeBranchUuid()).isEqualTo(pullRequest.getMergeBranchUuid()); + + // select a branch on another project with same branch name + assertThat(underTest.selectByPullRequestKey(dbSession, "U3", pullRequestId)).isEmpty(); } @Test diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java index b5904e6709f..7da1bce7caa 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java @@ -19,12 +19,18 @@ */ package org.sonar.db.component; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.protobuf.DbProjectBranches; import static org.assertj.core.api.Assertions.assertThat; public class BranchDtoTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private BranchDto underTest = new BranchDto(); @Test @@ -42,4 +48,30 @@ public class BranchDtoTest { assertThat(underTest.isMain()).isFalse(); } + + @Test + public void encode_and_decode_pull_request_data() { + String branch = "feature/pr1"; + String title = "Dummy Feature Title"; + String url = "http://example.com/pullRequests/pr1"; + + DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch) + .setTitle(title) + .setUrl(url) + .build(); + + underTest.setPullRequestData(pullRequestData); + + DbProjectBranches.PullRequestData decoded = underTest.getPullRequestData(); + assertThat(decoded).isNotNull(); + assertThat(decoded.getBranch()).isEqualTo(branch); + assertThat(decoded.getTitle()).isEqualTo(title); + assertThat(decoded.getUrl()).isEqualTo(url); + } + + @Test + public void getPullRequestData_returns_null_when_data_is_null() { + assertThat(underTest.getPullRequestData()).isNull(); + } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java index 709308d9ce6..0b36b57897f 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java @@ -128,4 +128,15 @@ public class ComponentDtoTest { assertThat(underTest.getKey()).isEqualTo("my_key"); assertThat(underTest.getBranch()).isNull(); } + + @Test + public void getKey_and_getPullRequest() { + ComponentDto underTest = new ComponentDto().setDbKey("my_key:PULL_REQUEST:pr-123"); + assertThat(underTest.getKey()).isEqualTo("my_key"); + assertThat(underTest.getPullRequest()).isEqualTo("pr-123"); + + underTest = new ComponentDto().setDbKey("my_key"); + assertThat(underTest.getKey()).isEqualTo("my_key"); + assertThat(underTest.getPullRequest()).isNull(); + } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java index bd1b66fd2f6..3166d3aab8b 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java @@ -34,6 +34,7 @@ import org.sonar.db.organization.OrganizationDto; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentKeyUpdaterDao.computeNewKey; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; @@ -106,6 +107,33 @@ public class ComponentKeyUpdaterDaoTest { } @Test + public void updateKey_updates_pull_requests_too() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + db.components().insertComponent(newFileDto(pullRequest)); + db.components().insertComponent(newFileDto(pullRequest)); + int branchComponentCount = 3; + + String oldProjectKey = project.getKey(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1); + + String oldBranchKey = pullRequest.getDbKey(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).hasSize(branchComponentCount); + + String newProjectKey = "newKey"; + String newBranchKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest()); + underTest.updateKey(dbSession, project.uuid(), newProjectKey); + + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).isEmpty(); + + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newBranchKey)).hasSize(branchComponentCount); + db.select(dbSession, "select kee from projects") + .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); + } + + @Test public void bulk_updateKey_updates_branches_too() { ComponentDto project = db.components().insertMainBranch(); ComponentDto branch = db.components().insertProjectBranch(project); @@ -133,6 +161,34 @@ public class ComponentKeyUpdaterDaoTest { .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); } + @Test + public void bulk_updateKey_updates_pull_requests_too() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(pullRequest), project.getKey())); + db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); + db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); + int branchComponentCount = 4; + + String oldProjectKey = project.getKey(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1); + + String oldPullRequestKey = pullRequest.getDbKey(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).hasSize(branchComponentCount); + + String newProjectKey = "newKey"; + String newPullRequestKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest()); + underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey); + + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty(); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).isEmpty(); + + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1); + assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newPullRequestKey)).hasSize(branchComponentCount); + db.select(dbSession, "select kee from projects") + .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); + } + private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) { return componentDto.setDbKey(key + ":" + componentDto.getDbKey()); } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java index 835ea641197..b0408ddb773 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java @@ -29,6 +29,7 @@ import org.sonar.db.organization.OrganizationDto; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; public class ComponentTesting { @@ -99,7 +100,15 @@ public class ComponentTesting { private static String generateKey(String key, ComponentDto parentModuleOrProject) { String branch = parentModuleOrProject.getBranch(); - return branch == null ? key : ComponentDto.generateBranchKey(key, branch); + if (branch != null) { + return ComponentDto.generateBranchKey(key, branch); + } + String pullRequest = parentModuleOrProject.getPullRequest(); + if (pullRequest != null) { + return ComponentDto.generatePullRequestKey(key, pullRequest); + } + + return key; } public static ComponentDto newModuleDto(ComponentDto subProjectOrProject) { @@ -231,6 +240,7 @@ public class ComponentTesting { checkArgument(project.qualifier().equals(Qualifiers.PROJECT)); checkArgument(project.getMainBranchProjectUuid() == null); String branchName = branchDto.getKey(); + String branchSeparator = branchDto.getBranchType() == PULL_REQUEST ? ":PULL_REQUEST:" : ":BRANCH:"; String uuid = branchDto.getUuid(); return new ComponentDto() .setUuid(uuid) @@ -240,7 +250,7 @@ public class ComponentTesting { .setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR) .setRootUuid(uuid) // name of the branch is not mandatory on the main branch - .setDbKey(branchName != null ? project.getDbKey() + ":BRANCH:" + branchName : project.getKey()) + .setDbKey(branchName != null ? project.getDbKey() + branchSeparator + branchName : project.getKey()) .setMainBranchProjectUuid(project.uuid()) .setName(project.name()) .setLongName(project.longName()) diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java new file mode 100644 index 00000000000..8b4137cf4dc --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java @@ -0,0 +1,46 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class AddKeyTypeInProjectBranches extends DdlChange { + + public static final String TABLE_NAME = "project_branches"; + + public AddKeyTypeInProjectBranches(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME) + .addColumn(VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("key_type") + .setIsNullable(true) + .setLimit(12) + .build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranches.java new file mode 100644 index 00000000000..2c6872a7fda --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranches.java @@ -0,0 +1,46 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.BlobColumnDef; +import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class AddPullRequestBinaryInProjectBranches extends DdlChange { + + static final String TABLE_NAME = "project_branches"; + static final String COLUMN_NAME = "pull_request_binary"; + + public AddPullRequestBinaryInProjectBranches(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME) + .addColumn(BlobColumnDef.newBlobColumnDefBuilder() + .setColumnName(COLUMN_NAME) + .setIsNullable(true) + .build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java index 9c406e6eb99..d8f5d82738c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java @@ -43,6 +43,12 @@ public class DbVersion71 implements DbVersion { .add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class) .add(2014, "Migrate webhooks from SETTINGS table to WEBHOOKS table", MigrateWebhooksToWebhooksTable.class) .add(2015, "Add webhook key to WEBHOOK_DELIVERIES table", AddWebhookKeyToWebhookDeliveriesTable.class) + .add(2016, "Increase branch type size in PROJECT_BRANCHES", IncreaseBranchTypeSizeForPullRequest.class) + .add(2017, "Add key_type column in PROJECT_BRANCHES", AddKeyTypeInProjectBranches.class) + .add(2018, "Fill key_type column in PROJECT_BRANCHES", SetKeyTypeToBranchInProjectBranches.class) + .add(2019, "Make key_type not nullable in PROJECT_BRANCHES", MakeKeyTypeNotNullableInProjectBranches.class) + .add(2020, "Replace index in PROJECT_BRANCHES", ReplaceIndexInProjectBranches.class) + .add(2021, "Add pull_request_data in PROJECT_BRANCHES", AddPullRequestBinaryInProjectBranches.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequest.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequest.java new file mode 100644 index 00000000000..a0c2df03090 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequest.java @@ -0,0 +1,45 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class IncreaseBranchTypeSizeForPullRequest extends DdlChange { + private static final String TABLE_NAME = "project_branches"; + + public IncreaseBranchTypeSizeForPullRequest(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AlterColumnsBuilder(getDialect(), TABLE_NAME) + .updateColumn(newVarcharColumnDefBuilder() + .setColumnName("branch_type") + .setLimit(12) + .build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranches.java new file mode 100644 index 00000000000..4c4951eaded --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranches.java @@ -0,0 +1,46 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class MakeKeyTypeNotNullableInProjectBranches extends DdlChange { + static final String TABLE_NAME = "project_branches"; + + public MakeKeyTypeNotNullableInProjectBranches(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AlterColumnsBuilder(getDialect(), TABLE_NAME) + .updateColumn(newVarcharColumnDefBuilder() + .setColumnName("key_type") + .setLimit(12) + .setIsNullable(false) + .build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranches.java new file mode 100644 index 00000000000..3fb289afdae --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranches.java @@ -0,0 +1,74 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.DropIndexBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class ReplaceIndexInProjectBranches extends DdlChange { + + static final String TABLE_NAME = "project_branches"; + private static final String OLD_INDEX_NAME = "project_branches_kee"; + static final String NEW_INDEX_NAME = "project_branches_kee_key_type"; + + static final VarcharColumnDef PROJECT_UUID_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("project_uuid") + .setIsNullable(false) + .setLimit(50) + .build(); + + static final VarcharColumnDef KEE_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("kee") + .setIsNullable(false) + .setLimit(255) + .build(); + + static final VarcharColumnDef KEY_TYPE_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName("key_type") + .setIsNullable(false) + .setLimit(12) + .build(); + + public ReplaceIndexInProjectBranches(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new DropIndexBuilder(getDialect()) + .setTable(TABLE_NAME) + .setName(OLD_INDEX_NAME) + .build()); + + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(PROJECT_UUID_COLUMN) + .addColumn(KEE_COLUMN) + .addColumn(KEY_TYPE_COLUMN) + .setUnique(true) + .setTable(TABLE_NAME) + .setName(NEW_INDEX_NAME) + .build() + ); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranches.java new file mode 100644 index 00000000000..7f3739fba80 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranches.java @@ -0,0 +1,53 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.sonar.api.utils.System2; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class SetKeyTypeToBranchInProjectBranches extends DataChange { + static final String TABLE_NAME = "project_branches"; + static final String DEFAULT_KEY_TYPE = "BRANCH"; + + private final System2 system2; + + public SetKeyTypeToBranchInProjectBranches(Database db, System2 system2) { + super(db); + this.system2 = system2; + } + + @Override + protected void execute(Context context) throws SQLException { + long now = system2.now(); + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.rowPluralName("branches"); + massUpdate.select("select uuid from " + TABLE_NAME + " where key_type is null"); + massUpdate.update("update " + TABLE_NAME + " set key_type=?, updated_at=? where uuid = ?"); + massUpdate.execute((row, update) -> { + update.setString(1, DEFAULT_KEY_TYPE); + update.setLong(2, now); + update.setString(3, row.getString(1)); + return true; + }); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest.java new file mode 100644 index 00000000000..b4b9ff02989 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest.java @@ -0,0 +1,56 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.VARCHAR; + +public class AddKeyTypeInProjectBranchesTest { + public static final String TABLE_NAME = "project_branches"; + + @Rule + public final CoreDbTester dbTester = CoreDbTester.createForSchema(AddKeyTypeInProjectBranchesTest.class, TABLE_NAME + ".sql"); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private AddKeyTypeInProjectBranches underTest = new AddKeyTypeInProjectBranches(dbTester.database()); + + @Test + public void column_is_added_to_table() throws SQLException { + underTest.execute(); + + dbTester.assertColumnDefinition(TABLE_NAME, "key_type", VARCHAR, null, true); + } + + @Test + public void migration_is_not_reentrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest.java new file mode 100644 index 00000000000..970b5ea6d42 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest.java @@ -0,0 +1,57 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.BLOB; +import static org.sonar.server.platform.db.migration.version.v71.AddPullRequestBinaryInProjectBranches.COLUMN_NAME; +import static org.sonar.server.platform.db.migration.version.v71.AddPullRequestBinaryInProjectBranches.TABLE_NAME; + +public class AddPullRequestBinaryInProjectBranchesTest { + + @Rule + public final CoreDbTester dbTester = CoreDbTester.createForSchema(AddPullRequestBinaryInProjectBranchesTest.class, TABLE_NAME + ".sql"); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private AddPullRequestBinaryInProjectBranches underTest = new AddPullRequestBinaryInProjectBranches(dbTester.database()); + + @Test + public void column_is_added_to_table() throws SQLException { + underTest.execute(); + + dbTester.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, BLOB, null, true); + } + + @Test + public void migration_is_not_reentrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java index 6b5041ef512..f50e8910b81 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java @@ -36,7 +36,7 @@ public class DbVersion71Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 16); + verifyMigrationCount(underTest, 22); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest.java new file mode 100644 index 00000000000..d9d121a3035 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest.java @@ -0,0 +1,67 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IncreaseBranchTypeSizeForPullRequestTest { + private static final String TABLE_NAME = "project_branches"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(IncreaseBranchTypeSizeForPullRequestTest.class, "project_branches.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private IncreaseBranchTypeSizeForPullRequest underTest = new IncreaseBranchTypeSizeForPullRequest(db.database()); + + @Test + public void cannot_insert_PULL_REQUEST_type_before_migration() { + expectedException.expect(IllegalStateException.class); + + insertRow(); + } + + @Test + public void can_insert_PULL_REQUEST_after_execute() throws SQLException { + underTest.execute(); + assertThat(db.countRowsOfTable(TABLE_NAME)).isEqualTo(0); + insertRow(); + assertThat(db.countRowsOfTable(TABLE_NAME)).isEqualTo(1); + } + + private void insertRow() { + db.executeInsert( + "PROJECT_BRANCHES", + "UUID", "dummy_uuid", + "PROJECT_UUID", "dummy_project_uuid", + "KEE", "dummy_key", + "CREATED_AT", 456789, + "UPDATED_AT", 456789, + "BRANCH_TYPE", "PULL_REQUEST"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest.java new file mode 100644 index 00000000000..7b7d7f35246 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest.java @@ -0,0 +1,61 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.rule.RuleScope; +import org.sonar.db.CoreDbTester; + +import static org.sonar.server.platform.db.migration.version.v71.MakeKeyTypeNotNullableInProjectBranches.TABLE_NAME; + +public class MakeKeyTypeNotNullableInProjectBranchesTest { + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(MakeKeyTypeNotNullableInProjectBranchesTest.class, "project_branches.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private MakeKeyTypeNotNullableInProjectBranches underTest = new MakeKeyTypeNotNullableInProjectBranches(db.database()); + + @Test + public void execute_makes_column_not_null() throws SQLException { + db.assertColumnDefinition(TABLE_NAME, "key_type", Types.VARCHAR, null, true); + insertRow(); + + underTest.execute(); + + db.assertColumnDefinition(TABLE_NAME, "key_type", Types.VARCHAR, null, false); + } + + private void insertRow() { + db.executeInsert( + "PROJECT_BRANCHES", + "UUID", "dummy_uuid", + "PROJECT_UUID", "dummy_project_uuid", + "KEE", "dummy_key", + "KEY_TYPE", "BRANCH", + "CREATED_AT", 456789, + "UPDATED_AT", 456789, + "BRANCH_TYPE", "BRANCH"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest.java new file mode 100644 index 00000000000..70a051073bc --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest.java @@ -0,0 +1,87 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +import static org.sonar.server.platform.db.migration.version.v71.ReplaceIndexInProjectBranches.NEW_INDEX_NAME; +import static org.sonar.server.platform.db.migration.version.v71.ReplaceIndexInProjectBranches.KEE_COLUMN; +import static org.sonar.server.platform.db.migration.version.v71.ReplaceIndexInProjectBranches.KEY_TYPE_COLUMN; +import static org.sonar.server.platform.db.migration.version.v71.ReplaceIndexInProjectBranches.PROJECT_UUID_COLUMN; +import static org.sonar.server.platform.db.migration.version.v71.ReplaceIndexInProjectBranches.TABLE_NAME; + +public class ReplaceIndexInProjectBranchesTest { + @Rule + public final CoreDbTester dbTester = CoreDbTester.createForSchema(ReplaceIndexInProjectBranchesTest.class, "project_branches.sql"); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ReplaceIndexInProjectBranches underTest = new ReplaceIndexInProjectBranches(dbTester.database()); + + @Test + public void column_is_part_of_index() throws SQLException { + underTest.execute(); + + dbTester.assertUniqueIndex(TABLE_NAME, NEW_INDEX_NAME, PROJECT_UUID_COLUMN.getName(), KEE_COLUMN.getName(), KEY_TYPE_COLUMN.getName()); + } + + @Test + public void adding_pr_with_same_key_as_existing_branch_fails_before_migration() { + expectedException.expect(IllegalStateException.class); + + String key = "feature/foo"; + insertBranch(1, key); + insertPullRequest(2, key); + } + + @Test + public void adding_pr_with_same_key_as_existing_branch_works_after_migration() throws SQLException { + underTest.execute(); + + String key = "feature/foo"; + insertBranch(1, key); + insertPullRequest(2, key); + } + + private void insertBranch(int id, String name) { + insertRow(id, "SHORT", name, "BRANCH"); + } + + private void insertPullRequest(int id, String pullRequestId) { + insertRow(id, "PULL_REQUEST", pullRequestId, "PULL_REQUEST"); + } + + private void insertRow(int id, String branchType, String key, String keyType) { + dbTester.executeInsert( + "PROJECT_BRANCHES", + "UUID", "dummy_uuid" + id, + "PROJECT_UUID", "dummy_project_uuid", + "KEE", key, + "KEY_TYPE", keyType, + "CREATED_AT", 456789 + id, + "UPDATED_AT", 456789 + id, + "BRANCH_TYPE", branchType); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest.java new file mode 100644 index 00000000000..c8438784529 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest.java @@ -0,0 +1,108 @@ +/* + * 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.platform.db.migration.version.v71.SetKeyTypeToBranchInProjectBranches.DEFAULT_KEY_TYPE; +import static org.sonar.server.platform.db.migration.version.v71.SetKeyTypeToBranchInProjectBranches.TABLE_NAME; + +public class SetKeyTypeToBranchInProjectBranchesTest { + private static final long PAST = 10_000_000_000L; + private static final long NOW = 50_000_000_000L; + + private System2 system2 = new TestSystem2().setNow(NOW); + + @Rule + public final CoreDbTester dbTester = CoreDbTester.createForSchema(SetKeyTypeToBranchInProjectBranchesTest.class, "project_branches.sql"); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private SetKeyTypeToBranchInProjectBranches underTest = new SetKeyTypeToBranchInProjectBranches(dbTester.database(), system2); + + @Test + public void has_no_effect_if_table_project_branches_is_empty() throws SQLException { + underTest.execute(); + + assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isEqualTo(0); + } + + @Test + public void updates_rows_to_BRANCH() throws SQLException { + insertRow(1, "SHORT"); + insertRow(2, "LONG"); + insertRow(3, "SHORT"); + insertRow(4, "LONG"); + + String countUpdatedAtSQL = "select count(uuid) from " + TABLE_NAME + " where updated_at = "; + + assertThat(countRowsWithValue(null)).isEqualTo(4); + assertThat(countRowsWithValue(DEFAULT_KEY_TYPE)).isEqualTo(0); + assertThat(dbTester.countSql(countUpdatedAtSQL + PAST)).isEqualTo(4); + + underTest.execute(); + + assertThat(countRowsWithValue(null)).isEqualTo(0); + assertThat(countRowsWithValue(DEFAULT_KEY_TYPE)).isEqualTo(4); + assertThat(dbTester.countSql(countUpdatedAtSQL + NOW)).isEqualTo(4); + } + + @Test + public void execute_is_reentreant() throws SQLException { + insertRow(1, "SHORT"); + insertRow(2, "LONG"); + insertRow(3, "SHORT"); + insertRow(4, "LONG"); + + underTest.execute(); + + underTest.execute(); + + assertThat(countRowsWithValue(null)).isEqualTo(0); + assertThat(countRowsWithValue(DEFAULT_KEY_TYPE)).isEqualTo(4); + } + + private int countRowsWithValue(@Nullable String value) { + if (value == null) { + return dbTester.countSql("select count(1) from " + TABLE_NAME + " where key_type is null"); + } + return dbTester.countSql("select count(1) from " + TABLE_NAME + " where key_type = '" + value + "'"); + } + + private void insertRow(int id, String branchType) { + dbTester.executeInsert( + "PROJECT_BRANCHES", + "UUID", "dummy_uuid" + id, + "PROJECT_UUID", "dummy_project_uuid" + id, + "KEE", "dummy_key" + id, + "CREATED_AT", PAST, + "UPDATED_AT", PAST, + "BRANCH_TYPE", branchType); + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest/project_branches.sql new file mode 100644 index 00000000000..7f1ef8adc6d --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest/project_branches.sql @@ -0,0 +1,11 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "BRANCH_TYPE" VARCHAR(12), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest/project_branches.sql new file mode 100644 index 00000000000..d3680968980 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest/project_branches.sql @@ -0,0 +1,12 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "KEY_TYPE" VARCHAR(12) NOT NULL, + "BRANCH_TYPE" VARCHAR(12), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE", "KEY_TYPE"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest/project_branches.sql new file mode 100644 index 00000000000..e05ff3bd2bb --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest/project_branches.sql @@ -0,0 +1,11 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "BRANCH_TYPE" VARCHAR(5), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest/project_branches.sql new file mode 100644 index 00000000000..6441ce1ca02 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest/project_branches.sql @@ -0,0 +1,12 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "KEY_TYPE" VARCHAR(12) NULL, + "BRANCH_TYPE" VARCHAR(12), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest/project_branches.sql new file mode 100644 index 00000000000..b7553cf6dcc --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest/project_branches.sql @@ -0,0 +1,12 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "KEY_TYPE" VARCHAR(12) NOT NULL, + "BRANCH_TYPE" VARCHAR(12), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest/project_branches.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest/project_branches.sql new file mode 100644 index 00000000000..6441ce1ca02 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest/project_branches.sql @@ -0,0 +1,12 @@ +CREATE TABLE "PROJECT_BRANCHES" ( + "UUID" VARCHAR(50) NOT NULL PRIMARY KEY, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "KEE" VARCHAR(255) NOT NULL, + "KEY_TYPE" VARCHAR(12) NULL, + "BRANCH_TYPE" VARCHAR(12), + "MERGE_BRANCH_UUID" VARCHAR(50), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); +CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE"); diff --git a/server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java b/server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java index c4f90f0d41d..13b6c4ae023 100644 --- a/server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java @@ -24,10 +24,12 @@ import org.sonar.api.config.Configuration; import org.sonar.api.config.Settings; import org.sonar.api.config.internal.ConfigurationBridge; import org.sonar.db.DbClient; +import org.sonar.db.component.BranchType; import org.sonar.server.computation.task.projectanalysis.analysis.Branch; import org.sonar.server.settings.ChildSettings; import static org.sonar.db.component.ComponentDto.generateBranchKey; +import static org.sonar.db.component.ComponentDto.generatePullRequestKey; @ComputeEngineSide public class ProjectConfigurationFactory { @@ -43,7 +45,11 @@ public class ProjectConfigurationFactory { public Configuration newProjectConfiguration(String projectKey, Branch branch) { Settings projectSettings = new ChildSettings(globalSettings); addSettings(projectSettings, projectKey); - addSettings(projectSettings, generateBranchKey(projectKey, branch.getName())); + if (branch.getType() == BranchType.PULL_REQUEST) { + addSettings(projectSettings, generatePullRequestKey(projectKey, branch.getPullRequestId())); + } else { + addSettings(projectSettings, generateBranchKey(projectKey, branch.getName())); + } return new ConfigurationBridge(projectSettings); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java b/server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java index 08cf30bae79..0fbac10bdb5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java @@ -55,7 +55,6 @@ import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY; import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY; -import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; import static org.sonar.api.measures.Metric.Level; import static org.sonar.api.measures.Metric.ValueType; import static org.sonar.api.measures.Metric.Level.ERROR; @@ -73,12 +72,14 @@ import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rat import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.valueOf; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonarqube.ws.MediaTypes.SVG; public class MeasureAction implements ProjectBadgesWsAction { private static final String PARAM_PROJECT = "project"; private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private static final String PARAM_METRIC = "metric"; private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder() @@ -140,6 +141,10 @@ public class MeasureAction implements ProjectBadgesWsAction { .createParam(PARAM_BRANCH) .setDescription("Branch key") .setExampleValue(KEY_BRANCH_EXAMPLE_001); + action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); action.createParam(PARAM_METRIC) .setDescription("Metric key") .setRequired(true) @@ -151,9 +156,10 @@ public class MeasureAction implements ProjectBadgesWsAction { response.stream().setMediaType(SVG); String projectKey = request.mandatoryParam(PARAM_PROJECT); String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); String metricKey = request.mandatoryParam(PARAM_METRIC); try (DbSession dbSession = dbClient.openSession(false)) { - ComponentDto project = componentFinder.getByKeyAndOptionalBranch(dbSession, projectKey, branch); + ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest); userSession.checkComponentPermission(USER, project); MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey); checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", metricKey); diff --git a/server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java b/server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java index 7b969514073..bdf45ad78c6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java @@ -41,12 +41,14 @@ import static org.apache.commons.io.IOUtils.write; import static org.sonar.api.web.UserRole.USER; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonarqube.ws.MediaTypes.SVG; public class QualityGateAction implements ProjectBadgesWsAction { private static final String PARAM_PROJECT = "project"; private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private final UserSession userSession; private final DbClient dbClient; @@ -76,6 +78,10 @@ public class QualityGateAction implements ProjectBadgesWsAction { .createParam(PARAM_BRANCH) .setDescription("Branch key") .setExampleValue(KEY_BRANCH_EXAMPLE_001); + action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -83,8 +89,9 @@ public class QualityGateAction implements ProjectBadgesWsAction { response.stream().setMediaType(SVG); String projectKey = request.mandatoryParam(PARAM_PROJECT); String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); try (DbSession dbSession = dbClient.openSession(false)) { - ComponentDto project = componentFinder.getByKeyAndOptionalBranch(dbSession, projectKey, branch); + ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest); userSession.checkComponentPermission(USER, project); Level qualityGateStatus = getQualityGate(dbSession, project); write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8); diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java index 0332472367f..a67d99799e1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java @@ -33,6 +33,7 @@ import org.sonarqube.ws.Batch.WsProjectResponse.FileData.Builder; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.writeProtobuf; public class ProjectAction implements BatchWsAction { @@ -41,6 +42,7 @@ public class ProjectAction implements BatchWsAction { private static final String PARAM_PROFILE = "profile"; private static final String PARAM_ISSUES_MODE = "issues_mode"; private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private final ProjectDataLoader projectDataLoader; @@ -79,6 +81,12 @@ public class ProjectAction implements BatchWsAction { .setSince("6.6") .setDescription("Branch key") .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + action + .createParam(PARAM_PULL_REQUEST) + .setSince("7.1") + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -87,7 +95,8 @@ public class ProjectAction implements BatchWsAction { .setModuleKey(wsRequest.mandatoryParam(PARAM_KEY)) .setProfileName(wsRequest.param(PARAM_PROFILE)) .setIssuesMode(wsRequest.mandatoryParamAsBoolean(PARAM_ISSUES_MODE)) - .setBranch(wsRequest.param(PARAM_BRANCH))); + .setBranch(wsRequest.param(PARAM_BRANCH)) + .setPullRequest(wsRequest.param(PARAM_PULL_REQUEST))); WsProjectResponse projectResponse = buildResponse(data); writeProtobuf(projectResponse, wsRequest, wsResponse); diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java index ea244900d6d..cdd7ec30ba1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java @@ -71,13 +71,15 @@ public class ProjectDataLoader { ProjectRepositories data = new ProjectRepositories(); String moduleKey = query.getModuleKey(); String branch = query.getBranch(); + String pullRequest = query.getPullRequest(); ComponentDto mainModule = componentFinder.getByKey(session, moduleKey); checkRequest(isProjectOrModule(mainModule), "Key '%s' belongs to a component which is not a Project", moduleKey); boolean hasScanPerm = userSession.hasComponentPermission(SCAN_EXECUTION, mainModule) || userSession.hasPermission(OrganizationPermission.SCAN, mainModule.getOrganizationUuid()); boolean hasBrowsePerm = userSession.hasComponentPermission(USER, mainModule); checkPermission(query.isIssuesMode(), hasScanPerm, hasBrowsePerm); - ComponentDto branchOrMainModule = branch == null ? mainModule : componentFinder.getByKeyAndBranch(session, moduleKey, branch); + ComponentDto branchOrMainModule = (branch == null && pullRequest == null) ? mainModule + : componentFinder.getByKeyAndOptionalBranchOrPullRequest(session, moduleKey, branch, pullRequest); ComponentDto project = getProject(branchOrMainModule, session); if (!project.getKey().equals(branchOrMainModule.getKey())) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java index 472be1f73c5..952a7b99b28 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java @@ -28,6 +28,7 @@ public class ProjectDataQuery { private String profileName; private boolean issuesMode; private String branch; + private String pullRequest; private ProjectDataQuery() { // No direct call @@ -71,6 +72,16 @@ public class ProjectDataQuery { return this; } + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public ProjectDataQuery setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + public static ProjectDataQuery create() { return new ProjectDataQuery(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java new file mode 100644 index 00000000000..644b75218f3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java @@ -0,0 +1,93 @@ +/* + * 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.branch.pr.ws; + +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.server.ws.WebService.NewController; +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.server.component.ComponentCleanerService; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.component.BranchType.PULL_REQUEST; +import static org.sonar.server.branch.pr.ws.PullRequestsWs.addProjectParam; +import static org.sonar.server.branch.pr.ws.PullRequestsWs.addPullRequestParam; +import static org.sonar.server.branch.pr.ws.PullRequestsWsParameters.PARAM_PROJECT; +import static org.sonar.server.branch.pr.ws.PullRequestsWsParameters.PARAM_PULL_REQUEST; +import static org.sonar.server.branch.ws.ProjectBranchesParameters.ACTION_DELETE; + +public class DeleteAction implements PullRequestWsAction { + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentCleanerService componentCleanerService; + private final ComponentFinder componentFinder; + + public DeleteAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, ComponentCleanerService componentCleanerService) { + this.dbClient = dbClient; + this.componentFinder = componentFinder; + this.userSession = userSession; + this.componentCleanerService = componentCleanerService; + } + + @Override + public void define(NewController context) { + WebService.NewAction action = context.createAction(ACTION_DELETE) + .setSince("7.1") + .setDescription("Delete a pull request.<br/>" + + "Requires 'Administer' rights on the specified project.") + .setPost(true) + .setHandler(this); + + addProjectParam(action); + addPullRequestParam(action); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + String pullRequestId = request.mandatoryParam(PARAM_PULL_REQUEST); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey); + checkPermission(project); + + BranchDto pullRequest = dbClient.branchDao().selectByPullRequestKey(dbSession, project.uuid(), pullRequestId) + .filter(branch -> branch.getBranchType() == PULL_REQUEST) + .orElseThrow(() -> new NotFoundException(String.format("Pull request '%s' is not found for project '%s'", pullRequestId, projectKey))); + + ComponentDto branchComponent = componentFinder.getByKeyAndPullRequest(dbSession, projectKey, pullRequest.getKey()); + componentCleanerService.deleteBranch(dbSession, branchComponent); + response.noContent(); + } + } + + private void checkPermission(ComponentDto project) { + userSession.checkComponentPermission(UserRole.ADMIN, project); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/ListAction.java new file mode 100644 index 00000000000..c6d63a10cfe --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/ListAction.java @@ -0,0 +1,142 @@ +/* + * 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.branch.pr.ws; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +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.component.SnapshotDto; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.issue.index.BranchStatistics; +import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.ProjectPullRequests; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.utils.DateUtils.formatDateTime; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.core.util.stream.MoreCollectors.toList; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; +import static org.sonar.db.component.BranchType.PULL_REQUEST; +import static org.sonar.server.branch.pr.ws.PullRequestsWs.addProjectParam; +import static org.sonar.server.branch.pr.ws.PullRequestsWsParameters.PARAM_PROJECT; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class ListAction implements PullRequestWsAction { + + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + private final IssueIndex issueIndex; + + public ListAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder, IssueIndex issueIndex) { + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + this.issueIndex = issueIndex; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("list") + .setSince("7.1") + .setDescription("List the pull requests of a project.<br/>" + + "Requires 'Administer' rights on the specified project.") + .setResponseExample(getClass().getResource("list-example.json")) + .setHandler(this); + + addProjectParam(action); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String projectKey = request.mandatoryParam(PARAM_PROJECT); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getByKey(dbSession, projectKey); + userSession.checkComponentPermission(UserRole.USER, project); + checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key"); + + List<BranchDto> pullRequests = dbClient.branchDao().selectByComponent(dbSession, project).stream() + .filter(b -> b.getBranchType() == PULL_REQUEST) + .collect(toList()); + List<String> pullRequestUuids = pullRequests.stream().map(BranchDto::getUuid).collect(toList()); + + Map<String, BranchDto> mergeBranchesByUuid = dbClient.branchDao() + .selectByUuids(dbSession, pullRequests.stream().map(BranchDto::getMergeBranchUuid).filter(Objects::nonNull).collect(toList())) + .stream().collect(uniqueIndex(BranchDto::getUuid)); + Map<String, BranchStatistics> branchStatisticsByBranchUuid = issueIndex.searchBranchStatistics(project.uuid(), pullRequestUuids).stream() + .collect(uniqueIndex(BranchStatistics::getBranchUuid, Function.identity())); + Map<String, String> analysisDateByBranchUuid = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, pullRequestUuids).stream() + .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> formatDateTime(s.getCreatedAt()))); + + ProjectPullRequests.ListWsResponse.Builder protobufResponse = ProjectPullRequests.ListWsResponse.newBuilder(); + pullRequests + .forEach(b -> addPullRequest(protobufResponse, b, mergeBranchesByUuid, branchStatisticsByBranchUuid.get(b.getUuid()), + analysisDateByBranchUuid.get(b.getUuid()))); + writeProtobuf(protobufResponse.build(), request, response); + } + } + + private static void addPullRequest(ProjectPullRequests.ListWsResponse.Builder response, BranchDto branch, Map<String, BranchDto> mergeBranchesByUuid, + BranchStatistics branchStatistics, @Nullable String analysisDate) { + Optional<BranchDto> mergeBranch = Optional.ofNullable(mergeBranchesByUuid.get(branch.getMergeBranchUuid())); + + ProjectPullRequests.PullRequest.Builder builder = ProjectPullRequests.PullRequest.newBuilder(); + builder.setKey(branch.getKey()); + + DbProjectBranches.PullRequestData pullRequestData = requireNonNull(branch.getPullRequestData(), "Pull request data should be available for branch type PULL_REQUEST"); + builder.setBranch(pullRequestData.getBranch()); + builder.setUrl(pullRequestData.getUrl()); + builder.setTitle(pullRequestData.getTitle()); + + if (mergeBranch.isPresent()) { + String mergeBranchKey = mergeBranch.get().getKey(); + builder.setBase(mergeBranchKey); + } else { + builder.setIsOrphan(true); + } + setNullable(analysisDate, builder::setAnalysisDate); + setBranchStatus(builder, branchStatistics); + response.addPullRequests(builder); + } + + private static void setBranchStatus(ProjectPullRequests.PullRequest.Builder builder, @Nullable BranchStatistics branchStatistics) { + ProjectPullRequests.Status.Builder statusBuilder = ProjectPullRequests.Status.newBuilder(); + statusBuilder.setBugs(branchStatistics == null ? 0L : branchStatistics.getBugs()); + statusBuilder.setVulnerabilities(branchStatistics == null ? 0L : branchStatistics.getVulnerabilities()); + statusBuilder.setCodeSmells(branchStatistics == null ? 0L : branchStatistics.getCodeSmells()); + builder.setStatus(statusBuilder); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsAction.java new file mode 100644 index 00000000000..2543d5d2d53 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsAction.java @@ -0,0 +1,26 @@ +/* + * 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.branch.pr.ws; + +import org.sonar.server.ws.WsAction; + +public interface PullRequestWsAction extends WsAction { + // marker interface +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsModule.java new file mode 100644 index 00000000000..35cc5226da4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsModule.java @@ -0,0 +1,32 @@ +/* + * 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.branch.pr.ws; + +import org.sonar.core.platform.Module; + +public class PullRequestWsModule extends Module { + @Override + protected void configureModule() { + add( + ListAction.class, + DeleteAction.class, + PullRequestsWs.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWs.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWs.java new file mode 100644 index 00000000000..0e63e78ff22 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWs.java @@ -0,0 +1,61 @@ +/* + * 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.branch.pr.ws; + +import org.sonar.api.server.ws.WebService; + +import static java.util.Arrays.stream; +import static org.sonar.server.branch.pr.ws.PullRequestsWsParameters.PARAM_PROJECT; +import static org.sonar.server.branch.pr.ws.PullRequestsWsParameters.PARAM_PULL_REQUEST; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; + +public class PullRequestsWs implements WebService { + private final PullRequestWsAction[] actions; + + public PullRequestsWs(PullRequestWsAction... actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/project_pull_requests") + .setSince("7.1") + .setDescription("Manage pull request (only available when the Branch plugin is installed)"); + stream(actions).forEach(action -> action.define(controller)); + controller.done(); + } + + static void addProjectParam(NewAction action) { + action + .createParam(PARAM_PROJECT) + .setDescription("Project key") + .setExampleValue(KEY_PROJECT_EXAMPLE_001) + .setRequired(true); + } + + static void addPullRequestParam(NewAction action) { + action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue("1543") + .setRequired(true); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWsParameters.java new file mode 100644 index 00000000000..809aef3616e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWsParameters.java @@ -0,0 +1,31 @@ +/* + * 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.branch.pr.ws; + +public class PullRequestsWsParameters { + + public static final String PARAM_PROJECT = "project"; + public static final String PARAM_COMPONENT = "component"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; + + private PullRequestsWsParameters() { + // static utility class + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/package-info.java new file mode 100644 index 00000000000..e96a6dab0f8 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.branch.pr.ws; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java index c4e34de7503..cde6921b059 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java @@ -19,7 +19,6 @@ */ package org.sonar.server.branch.ws; -import com.google.common.io.Resources; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -35,10 +34,10 @@ import org.sonar.server.user.UserSession; import static org.sonar.server.branch.ws.BranchesWs.addBranchParam; import static org.sonar.server.branch.ws.BranchesWs.addProjectParam; -import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonar.server.branch.ws.ProjectBranchesParameters.ACTION_DELETE; import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_BRANCH; import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_PROJECT; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; public class DeleteAction implements BranchWsAction { private final DbClient dbClient; @@ -59,7 +58,6 @@ public class DeleteAction implements BranchWsAction { .setSince("6.6") .setDescription("Delete a non-main branch of a project.<br/>" + "Requires 'Administer' rights on the specified project.") - .setResponseExample(Resources.getResource(getClass(), "list-example.json")) .setPost(true) .setHandler(this); @@ -78,7 +76,7 @@ public class DeleteAction implements BranchWsAction { checkPermission(project); BranchDto branch = checkFoundWithOptional( - dbClient.branchDao().selectByKey(dbSession, project.uuid(), branchKey), + dbClient.branchDao().selectByBranchKey(dbSession, project.uuid(), branchKey), "Branch '%s' not found for project '%s'", branchKey, projectKey); if (branch.isMain()) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java index 4583eb92be9..472a2a4175f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java @@ -21,17 +21,18 @@ package org.sonar.server.branch.ws; import com.google.common.io.Resources; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.web.UserRole; import org.sonar.core.util.Protobuf; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.BranchDto; @@ -99,30 +100,34 @@ public class ListAction implements BranchWsAction { checkPermission(project); checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key"); - Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project); + Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project).stream() + .filter(b -> b.getBranchType() == SHORT || b.getBranchType() == LONG) + .collect(MoreCollectors.toList()); + List<String> branchUuids = branches.stream().map(BranchDto::getUuid).collect(toList()); + Map<String, BranchDto> mergeBranchesByUuid = dbClient.branchDao() .selectByUuids(dbSession, branches.stream().map(BranchDto::getMergeBranchUuid).filter(Objects::nonNull).collect(toList())) .stream().collect(uniqueIndex(BranchDto::getUuid)); Map<String, LiveMeasureDto> qualityGateMeasuresByComponentUuids = dbClient.liveMeasureDao() - .selectByComponentUuidsAndMetricKeys(dbSession, branches.stream().map(BranchDto::getUuid).collect(toList()), singletonList(ALERT_STATUS_KEY)) - .stream().collect(uniqueIndex(LiveMeasureDto::getComponentUuid)); + .selectByComponentUuidsAndMetricKeys(dbSession, branchUuids, singletonList(ALERT_STATUS_KEY)).stream() + .collect(uniqueIndex(LiveMeasureDto::getComponentUuid)); Map<String, BranchStatistics> branchStatisticsByBranchUuid = issueIndex.searchBranchStatistics(project.uuid(), branches.stream() .filter(b -> b.getBranchType().equals(SHORT)) - .map(BranchDto::getUuid).collect(toList())) - .stream().collect(uniqueIndex(BranchStatistics::getBranchUuid, Function.identity())); + .map(BranchDto::getUuid).collect(toList())).stream() + .collect(uniqueIndex(BranchStatistics::getBranchUuid, Function.identity())); Map<String, String> analysisDateByBranchUuid = dbClient.snapshotDao() - .selectLastAnalysesByRootComponentUuids(dbSession, branches.stream().map(BranchDto::getUuid).collect(Collectors.toList())) - .stream().collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> formatDateTime(s.getCreatedAt()))); + .selectLastAnalysesByRootComponentUuids(dbSession, branchUuids).stream() + .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> formatDateTime(s.getCreatedAt()))); ProjectBranches.ListWsResponse.Builder protobufResponse = ProjectBranches.ListWsResponse.newBuilder(); branches.forEach(b -> addBranch(protobufResponse, b, mergeBranchesByUuid, qualityGateMeasuresByComponentUuids.get(b.getUuid()), branchStatisticsByBranchUuid.get(b.getUuid()), - analysisDateByBranchUuid.get(b.getUuid()))); + analysisDateByBranchUuid.get(b.getUuid()))); WsUtils.writeProtobuf(protobufResponse.build(), request, response); } } private static void addBranch(ProjectBranches.ListWsResponse.Builder response, BranchDto branch, Map<String, BranchDto> mergeBranchesByUuid, - @Nullable LiveMeasureDto qualityGateMeasure, BranchStatistics branchStatistics, @Nullable String analysisDate) { + @Nullable LiveMeasureDto qualityGateMeasure, BranchStatistics branchStatistics, @Nullable String analysisDate) { ProjectBranches.Branch.Builder builder = toBranchBuilder(branch, Optional.ofNullable(mergeBranchesByUuid.get(branch.getMergeBranchUuid()))); setBranchStatus(builder, branch, qualityGateMeasure, branchStatistics); if (analysisDate != null) { @@ -137,7 +142,7 @@ public class ListAction implements BranchWsAction { setNullable(branchKey, builder::setName); builder.setIsMain(branch.isMain()); builder.setType(Common.BranchType.valueOf(branch.getBranchType().name())); - if (branch.getBranchType().equals(SHORT)) { + if (branch.getBranchType() == SHORT) { if (mergeBranch.isPresent()) { String mergeBranchKey = mergeBranch.get().getKey(); builder.setMergeBranch(mergeBranchKey); @@ -149,8 +154,8 @@ public class ListAction implements BranchWsAction { } private static void setBranchStatus(ProjectBranches.Branch.Builder builder, BranchDto branch, @Nullable LiveMeasureDto qualityGateMeasure, - @Nullable BranchStatistics branchStatistics) { - ProjectBranches.Branch.Status.Builder statusBuilder = ProjectBranches.Branch.Status.newBuilder(); + @Nullable BranchStatistics branchStatistics) { + ProjectBranches.Status.Builder statusBuilder = ProjectBranches.Status.newBuilder(); if (branch.getBranchType() == LONG && qualityGateMeasure != null) { Protobuf.setNullable(qualityGateMeasure.getDataAsString(), statusBuilder::setQualityGateStatus); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java index fa433fbec57..b2efad6dacf 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java @@ -77,7 +77,7 @@ public class RenameAction implements BranchWsAction { ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey); checkPermission(project); - Optional<BranchDto> existingBranch = dbClient.branchDao().selectByKey(dbSession, project.uuid(), newBranchName); + Optional<BranchDto> existingBranch = dbClient.branchDao().selectByBranchKey(dbSession, project.uuid(), newBranchName); checkArgument(!existingBranch.filter(b -> !b.isMain()).isPresent(), "Impossible to update branch name: a branch with name \"%s\" already exists in the project.", newBranchName); diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java index 0d8bf400b83..dcbe6290f97 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java @@ -100,7 +100,8 @@ public class ActivityAction implements CeWsAction { .setChangelog( new Change("5.5", "it's no more possible to specify the page parameter."), new Change("6.1", "field \"logs\" is deprecated and its value is always false"), - new Change("6.6", "fields \"branch\" and \"branchType\" added")) + new Change("6.6", "fields \"branch\" and \"branchType\" added"), + new Change("7.1", "fields \"pullRequest\" and \"pullRequestTitle\" added")) .setSince("5.2"); action.createParam(PARAM_COMPONENT_ID) diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java index fe4a1e3bc68..23f1ba3e091 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java @@ -89,7 +89,7 @@ public class TaskFormatter { builder.setSubmittedAt(formatDateTime(new Date(dto.getCreatedAt()))); setNullable(dto.getStartedAt(), builder::setStartedAt, DateUtils::formatDateTime); setNullable(computeExecutionTimeMs(dto), builder::setExecutionTimeMs); - setBranch(builder, dto.getUuid(), componentDtoCache); + setBranchOrPullRequest(builder, dto.getUuid(), componentDtoCache); return builder.build(); } @@ -115,10 +115,8 @@ public class TaskFormatter { builder.setLogs(false); setNullable(dto.getComponentUuid(), uuid -> setComponent(builder, uuid, componentDtoCache).setComponentId(uuid)); String analysisUuid = dto.getAnalysisUuid(); - if (analysisUuid != null) { - builder.setAnalysisId(analysisUuid); - } - setBranch(builder, dto.getUuid(), componentDtoCache); + setNullable(analysisUuid, builder::setAnalysisId); + setBranchOrPullRequest(builder, dto.getUuid(), componentDtoCache); setNullable(analysisUuid, builder::setAnalysisId); setNullable(dto.getSubmitterLogin(), builder::setSubmitterLogin); builder.setSubmittedAt(formatDateTime(new Date(dto.getSubmittedAt()))); @@ -144,13 +142,22 @@ public class TaskFormatter { return builder; } - private static Ce.Task.Builder setBranch(Ce.Task.Builder builder, String taskUuid, DtoCache componentDtoCache) { - componentDtoCache.getBranchName(taskUuid).ifPresent( + private static Ce.Task.Builder setBranchOrPullRequest(Ce.Task.Builder builder, String taskUuid, DtoCache componentDtoCache) { + componentDtoCache.getBranchKey(taskUuid).ifPresent( b -> { - builder.setBranch(b); - builder.setBranchType(componentDtoCache.getBranchType(taskUuid) - .orElseThrow(() -> new IllegalStateException(format("Could not find branch type of task '%s'", taskUuid)))); + Common.BranchType branchType = componentDtoCache.getBranchType(taskUuid) + .orElseThrow(() -> new IllegalStateException(format("Could not find branch type of task '%s'", taskUuid))); + switch (branchType) { + case LONG: + case SHORT: + builder.setBranchType(branchType); + builder.setBranch(b); + break; + default: + throw new IllegalStateException(String.format("Unknown branch type '%s'", branchType)); + } }); + componentDtoCache.getPullRequest(taskUuid).ifPresent(builder::setPullRequest); return builder; } @@ -237,7 +244,7 @@ public class TaskFormatter { return organizationDto.getKey(); } - Optional<String> getBranchName(String taskUuid) { + Optional<String> getBranchKey(String taskUuid) { return characteristicsByTaskUuid.get(taskUuid).stream() .filter(c -> c.getKey().equals(CeTaskCharacteristicDto.BRANCH_KEY)) .map(CeTaskCharacteristicDto::getValue) @@ -250,6 +257,13 @@ public class TaskFormatter { .map(c -> Common.BranchType.valueOf(c.getValue())) .findAny(); } + + Optional<String> getPullRequest(String taskUuid) { + return characteristicsByTaskUuid.get(taskUuid).stream() + .filter(c -> c.getKey().equals(CeTaskCharacteristicDto.PULL_REQUEST)) + .map(CeTaskCharacteristicDto::getValue) + .findAny(); + } } /** 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 5a2568bdd7c..a1e6d94c628 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 @@ -154,8 +154,24 @@ public class ComponentFinder { throw new NotFoundException(format("Component '%s' on branch '%s' not found", key, branch)); } - public ComponentDto getByKeyAndOptionalBranch(DbSession dbSession, String key, @Nullable String branch) { - return branch == null ? getByKey(dbSession, key) : getByKeyAndBranch(dbSession, key, branch); + public ComponentDto getByKeyAndPullRequest(DbSession dbSession, String key, String pullRequest) { + java.util.Optional<ComponentDto> componentDto = dbClient.componentDao().selectByKeyAndPullRequest(dbSession, key, pullRequest); + if (componentDto.isPresent() && componentDto.get().isEnabled()) { + return componentDto.get(); + } + throw new NotFoundException(format("Component '%s' of pull request '%s' not found", key, pullRequest)); + } + + public ComponentDto getByKeyAndOptionalBranchOrPullRequest(DbSession dbSession, String key, @Nullable String branch, @Nullable String pullRequest) { + checkArgument(branch == null || pullRequest == null, "Either branch or pull request can be provided, not both"); + if (branch != null) { + return getByKeyAndBranch(dbSession, key, branch); + } + if (pullRequest != null) { + return getByKeyAndPullRequest(dbSession, key, pullRequest); + } + + return getByKey(dbSession, key); } public enum ParamNames { @@ -165,7 +181,7 @@ public class ComponentFinder { UUID_AND_KEY("uuid", "key"), ID_AND_KEY("id", "key"), COMPONENT_ID_AND_KEY("componentId", "componentKey"), - BASE_COMPONENT_ID_AND_KEY("baseComponentId", "baseComponentKey"), + BASE_COMPONENT_ID_AND_KEY("baseComponentId", "component"), DEVELOPER_ID_AND_KEY("developerId", "developerKey"), COMPONENT_ID_AND_COMPONENT("componentId", "component"), PROJECT_ID_AND_PROJECT("projectId", "project"), diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java index 562ffd31f7a..f9ca3b8e556 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java @@ -57,9 +57,11 @@ import static org.sonar.api.measures.CoreMetrics.VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; -import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; public class AppAction implements ComponentsWsAction { @@ -110,6 +112,12 @@ public class AppAction implements ComponentsWsAction { .setSince("6.6") .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setSince("7.1") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -131,12 +139,14 @@ public class AppAction implements ComponentsWsAction { private ComponentDto loadComponent(DbSession dbSession, Request request) { String componentUuid = request.param(PARAM_COMPONENT_ID); - String branch = request.param("branch"); - checkArgument(componentUuid == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH); - if (branch == null) { + String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); + checkArgument(componentUuid == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID, + PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param(PARAM_COMPONENT), COMPONENT_ID_AND_COMPONENT); } - return componentFinder.getByKeyAndOptionalBranch(dbSession, request.mandatoryParam(PARAM_COMPONENT), branch); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, request.mandatoryParam(PARAM_COMPONENT), branch, pullRequest); } private void appendComponent(JsonWriter json, ComponentDto component, UserSession userSession, DbSession session) { @@ -168,6 +178,10 @@ public class AppAction implements ComponentsWsAction { if (branch != null) { json.prop("branch", branch); } + String pullRequest = project.getPullRequest(); + if (pullRequest != null) { + json.prop("pullRequest", pullRequest); + } json.prop("fav", isFavourite); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java index b03f5d26d1d..5adde10f4f4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java @@ -62,6 +62,7 @@ class ComponentDtoToWsComponent { .setName(dto.name()) .setQualifier(dto.qualifier()); setNullable(emptyToNull(dto.getBranch()), wsComponent::setBranch); + setNullable(emptyToNull(dto.getPullRequest()), wsComponent::setPullRequest); setNullable(emptyToNull(dto.path()), wsComponent::setPath); setNullable(emptyToNull(dto.description()), wsComponent::setDescription); setNullable(emptyToNull(dto.language()), wsComponent::setLanguage); diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java index d101548ab74..19de6f14947 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java @@ -35,6 +35,7 @@ public class MeasuresWsParameters { public static final String DEPRECATED_PARAM_BASE_COMPONENT_KEY = "baseComponentKey"; public static final String PARAM_COMPONENT = "component"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; public static final String PARAM_STRATEGY = "strategy"; public static final String PARAM_QUALIFIERS = "qualifiers"; public static final String PARAM_METRICS = "metrics"; diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java index 92631450f7f..6da99f6b353 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java @@ -22,6 +22,8 @@ package org.sonar.server.component.ws; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -35,21 +37,21 @@ import org.sonar.server.component.ComponentFinder; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Components.ShowWsResponse; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT; import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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; import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; -import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; public class ShowAction implements ComponentsWsAction { private final UserSession userSession; @@ -97,6 +99,12 @@ public class ShowAction implements ComponentsWsAction { .setExampleValue(KEY_BRANCH_EXAMPLE_001) .setInternal(true) .setSince("6.6"); + + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); } @Override @@ -110,6 +118,7 @@ public class ShowAction implements ComponentsWsAction { private ShowWsResponse doHandle(Request request) { try (DbSession dbSession = dbClient.openSession(false)) { ComponentDto component = loadComponent(dbSession, request); + userSession.checkComponentPermission(UserRole.USER, component); Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.projectUuid()); List<ComponentDto> ancestors = dbClient.componentDao().selectAncestors(dbSession, component); OrganizationDto organizationDto = componentFinder.getOrganization(dbSession, component); @@ -121,12 +130,14 @@ public class ShowAction implements ComponentsWsAction { String componentId = request.getId(); String componentKey = request.getKey(); String branch = request.getBranch(); - checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH); - ComponentDto component = branch == null - ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT) - : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); - userSession.checkComponentPermission(UserRole.USER, component); - return component; + String pullRequest = request.getPullRequest(); + checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID, + PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT); + } + checkRequest(componentKey!=null, "The '%s' parameter is missing", PARAM_COMPONENT); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } private static ShowWsResponse buildResponse(ComponentDto component, OrganizationDto organizationDto, List<ComponentDto> orderedAncestors, Optional<SnapshotDto> lastAnalysis) { @@ -144,13 +155,15 @@ public class ShowAction implements ComponentsWsAction { return new Request() .setId(request.param(PARAM_COMPONENT_ID)) .setKey(request.param(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)); + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)); } private static class Request { private String id; private String key; private String branch; + private String pullRequest; @CheckForNull public String getId() { @@ -181,5 +194,15 @@ public class ShowAction implements ComponentsWsAction { this.branch = branch; return this; } + + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public Request setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java index 5187b0cc797..ef86ba3c9c7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java @@ -66,15 +66,18 @@ import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; -import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext; +import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter; +import static org.sonar.server.ws.WsUtils.checkRequest; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_TREE; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY; -import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; public class TreeAction implements ComponentsWsAction { @@ -138,6 +141,12 @@ public class TreeAction implements ComponentsWsAction { .setInternal(true) .setSince("6.6"); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); + action.createSortParams(SORTS, NAME_SORT, true) .setDescription("Comma-separated list of sort fields") .setExampleValue(NAME_SORT + ", " + PATH_SORT); @@ -190,12 +199,16 @@ public class TreeAction implements ComponentsWsAction { private ComponentDto loadComponent(DbSession dbSession, Request request) { String componentId = request.getBaseComponentId(); - String componentKey = request.getBaseComponentKey(); + String componentKey = request.getComponent(); String branch = request.getBranch(); - checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH); - return branch == null - ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT) - : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); + String pullRequest = request.getPullRequest(); + checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID, + PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT); + } + checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } private Map<String, ComponentDto> searchReferenceComponentsByUuid(DbSession dbSession, List<ComponentDto> components) { @@ -285,8 +298,9 @@ public class TreeAction implements ComponentsWsAction { private static Request toTreeWsRequest(org.sonar.api.server.ws.Request request) { return new Request() .setBaseComponentId(request.param(PARAM_COMPONENT_ID)) - .setBaseComponentKey(request.param(PARAM_COMPONENT)) + .setComponent(request.param(PARAM_COMPONENT)) .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) .setStrategy(request.mandatoryParam(PARAM_STRATEGY)) .setQuery(request.param(Param.TEXT_QUERY)) .setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS)) @@ -335,9 +349,9 @@ public class TreeAction implements ComponentsWsAction { private static class Request { private String baseComponentId; - private String baseComponentKey; private String component; private String branch; + private String pullRequest; private String strategy; private List<String> qualifiers; private String query; @@ -364,24 +378,6 @@ public class TreeAction implements ComponentsWsAction { return this; } - /** - * @deprecated since 6.4, please use {@link #getComponent()} instead - */ - @Deprecated - @CheckForNull - private String getBaseComponentKey() { - return baseComponentKey; - } - - /** - * @deprecated since 6.4, please use {@link #setComponent(String)} instead - */ - @Deprecated - private Request setBaseComponentKey(@Nullable String baseComponentKey) { - this.baseComponentKey = baseComponentKey; - return this; - } - public Request setComponent(@Nullable String component) { this.component = component; return this; @@ -403,6 +399,16 @@ public class TreeAction implements ComponentsWsAction { } @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public Request setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + @CheckForNull private String getStrategy() { return strategy; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java index 97159e9875d..77738cc6ffb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java @@ -83,6 +83,13 @@ public interface AnalysisMetadataHolder { boolean isLongLivingBranch(); /** + * Convenience method equivalent to do the check using {@link #getBranch()} + * + * @throws IllegalStateException if branch has not been set + */ + boolean isPullRequest(); + + /** * @throws IllegalStateException if cross project duplication flag has not been set */ boolean isCrossProjectDuplicationEnabled(); @@ -93,6 +100,13 @@ public interface AnalysisMetadataHolder { Branch getBranch(); /** + * In a pull request analysis, return the ID of the pull request + * + * @throws IllegalStateException if current analysis is not a pull request + */ + String getPullRequestId(); + + /** * The project as represented by the main branch. It is used to load settings * like Quality gates, webhooks and configuration. * @@ -119,5 +133,4 @@ public interface AnalysisMetadataHolder { * Plugins used during the analysis on scanner side */ Map<String, ScannerPlugin> getScannerPluginsByKey(); - } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java index c26f123efdf..6fee4c5f70c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java @@ -39,6 +39,7 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder private final InitializedProperty<Analysis> baseProjectSnapshot = new InitializedProperty<>(); private final InitializedProperty<Boolean> crossProjectDuplicationEnabled = new InitializedProperty<>(); private final InitializedProperty<Branch> branch = new InitializedProperty<>(); + private final InitializedProperty<String> pullRequestId = new InitializedProperty<>(); private final InitializedProperty<Project> project = new InitializedProperty<>(); private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); @@ -149,6 +150,19 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder } @Override + public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) { + checkState(!this.pullRequestId.isInitialized(), "Pull request id has already been set"); + this.pullRequestId.setProperty(pullRequestId); + return this; + } + + @Override + public String getPullRequestId() { + checkState(pullRequestId.isInitialized(), "Pull request id has not been set"); + return pullRequestId.getProperty(); + } + + @Override public MutableAnalysisMetadataHolder setProject(Project project) { checkState(!this.project.isInitialized(), "Project has already been set"); this.project.setProperty(project); @@ -201,16 +215,25 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder return pluginsByKey.getProperty(); } + @Override public boolean isShortLivingBranch() { checkState(this.branch.isInitialized(), BRANCH_NOT_SET); Branch prop = branch.getProperty(); return prop != null && prop.getType() == BranchType.SHORT; } + @Override public boolean isLongLivingBranch() { checkState(this.branch.isInitialized(), BRANCH_NOT_SET); Branch prop = branch.getProperty(); return prop != null && prop.getType() == BranchType.LONG; } + @Override + public boolean isPullRequest() { + checkState(this.branch.isInitialized(), BRANCH_NOT_SET); + Branch prop = branch.getProperty(); + return prop != null && prop.getType() == BranchType.PULL_REQUEST; + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java index f3ebcc87e80..bc7fff0f769 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java @@ -53,4 +53,9 @@ public interface Branch extends ComponentKeyGenerator { * or not. */ boolean supportsCrossProjectCpd(); + + /** + * @throws IllegalStateException if this branch configuration is not a pull request. + */ + String getPullRequestId(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java index b4e4e541cfd..09acc83d648 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java @@ -61,6 +61,11 @@ public interface MutableAnalysisMetadataHolder extends AnalysisMetadataHolder { MutableAnalysisMetadataHolder setBranch(Branch branch); /** + * @throws IllegalStateException if pull request id has already been set + */ + MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId); + + /** * @throws IllegalStateException if project has already been set */ MutableAnalysisMetadataHolder setProject(Project project); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java index 8af751c52bc..e7f179b283c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java @@ -54,6 +54,7 @@ import static java.util.Optional.of; import static java.util.Optional.ofNullable; import static org.sonar.api.ce.posttask.CeTask.Status.FAILED; import static org.sonar.api.ce.posttask.CeTask.Status.SUCCESS; +import static org.sonar.db.component.BranchType.PULL_REQUEST; /** * Responsible for calling {@link PostProjectAnalysisTask} implementations (if any). @@ -180,7 +181,8 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor private BranchImpl createBranch() { org.sonar.server.computation.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch(); if (!analysisBranch.isLegacyFeature()) { - return new BranchImpl(analysisBranch.isMain(), analysisBranch.getName(), Branch.Type.valueOf(analysisBranch.getType().name())); + String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestId() : analysisBranch.getName(); + return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name())); } return null; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java index 1ed44701a51..a58a8ca987a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java @@ -25,7 +25,9 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; +import org.sonar.db.protobuf.DbProjectBranches; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.task.projectanalysis.analysis.Branch; @@ -55,12 +57,11 @@ public class BranchPersisterImpl implements BranchPersister { if (branch.isMain()) { checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis"); branchComponentDto = branchComponentDtoOpt.get(); - } else { // inserts new row in table projects if it's the first time branch is analyzed branchComponentDto = branchComponentDtoOpt.or(() -> insertIntoProjectsTable(dbSession, branchUuid)); - } + // insert or update in table project_branches dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch)); } @@ -75,15 +76,29 @@ public class BranchPersisterImpl implements BranchPersister { return (first != null) ? first : second; } - private static BranchDto toBranchDto(ComponentDto componentDto, Branch branch) { + private BranchDto toBranchDto(ComponentDto componentDto, Branch branch) { BranchDto dto = new BranchDto(); dto.setUuid(componentDto.uuid()); + // MainBranchProjectUuid will be null if it's a main branch dto.setProjectUuid(firstNonNull(componentDto.getMainBranchProjectUuid(), componentDto.projectUuid())); - dto.setKey(branch.getName()); dto.setBranchType(branch.getType()); + // merge branch is only present if it's a short living branch dto.setMergeBranchUuid(branch.getMergeBranchUuid().orElse(null)); + + if (branch.getType() == BranchType.PULL_REQUEST) { + dto.setKey(analysisMetadataHolder.getPullRequestId()); + + DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder() + .setBranch(branch.getName()) + .setTitle(branch.getName()) + .build(); + dto.setPullRequestData(pullRequestData); + } else { + dto.setKey(branch.getName()); + } + return dto; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java index d3e76444576..63b49e588ef 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java @@ -86,6 +86,11 @@ public class DefaultBranchImpl implements Branch { } @Override + public String getPullRequestId() { + throw new IllegalStateException("Only a branch of type PULL_REQUEST can have a pull request id."); + } + + @Override public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) { String moduleWithBranch = module.getKey(); if (isLegacyBranch) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java index 5d12477afea..1ec0b9e1d3b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java @@ -31,7 +31,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import static com.google.common.base.Preconditions.checkState; -import static org.sonar.db.component.ComponentDto.removeBranchFromKey; +import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey; /** * Cache a map between component keys and uuids in the merge branch @@ -74,7 +74,7 @@ public class MergeBranchComponentUuids { @CheckForNull public String getUuid(String dbKey) { lazyInit(); - String cleanComponentKey = removeBranchFromKey(dbKey); + String cleanComponentKey = removeBranchAndPullRequestFromKey(dbKey); return uuidsByKey.get(cleanComponentKey); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java index eb799f1ad8f..71d97f257c5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java @@ -29,7 +29,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.KeyWithUuidDto; -import static org.sonar.db.component.ComponentDto.removeBranchFromKey; +import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey; /** * Cache a map of component key -> uuid in short branches that have issues with status either RESOLVED or CONFIRMED. @@ -51,7 +51,7 @@ public class ShortBranchComponentsWithIssues { try (DbSession dbSession = dbClient.openSession(false)) { List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid); for (KeyWithUuidDto dto : components) { - uuidsByKey.computeIfAbsent(removeBranchFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); + uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid()); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java index e9403cf007a..c60c9abade4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java @@ -44,7 +44,7 @@ public class IssueTrackingDelegator { } public TrackingResult track(Component component) { - if (analysisMetadataHolder.isShortLivingBranch()) { + if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) { return standardResult(shortBranchTracker.track(component)); } else if (isFirstAnalysisSecondaryLongLivingBranch()) { Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java index a69113a554d..4d86d87b61e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java @@ -50,7 +50,7 @@ public class ShortBranchIssuesLoader { } public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) { - String componentKey = ComponentDto.removeBranchFromKey(component.getKey()); + String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getKey()); Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey); if (uuids.isEmpty()) { return Collections.emptyList(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java index 7838e9091c2..89b97b839c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java @@ -67,7 +67,7 @@ public class LoadQualityGateStep implements ComputationStep { } private Optional<QualityGate> getShortLivingBranchQualityGate() { - if (analysisMetadataHolder.isShortLivingBranch()) { + if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) { Optional<QualityGate> qualityGate = qualityGateService.findById(ShortLivingBranchQualityGate.ID); if (qualityGate.isPresent()) { return qualityGate; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java index fe9b452b603..d9bbf514fc5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java @@ -69,8 +69,8 @@ public class QualityGateEventsStep implements ComputationStep { @Override public void execute() { - // no notification on short living branch as there is no real Quality Gate on those - if (analysisMetadataHolder.isShortLivingBranch()) { + // no notification on short living branch and pull request as there is no real Quality Gate on those + if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) { return; } new DepthTraversalTypeAwareCrawler( diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java index 5addea9d4ca..744a49f7291 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; +import javax.annotation.CheckForNull; import org.sonar.api.issue.Issue; import org.sonar.api.utils.Duration; import org.sonar.core.issue.DefaultIssue; @@ -49,6 +50,7 @@ import org.sonar.server.issue.notification.NewIssuesNotificationFactory; import org.sonar.server.issue.notification.NewIssuesStatistics; import org.sonar.server.notification.NotificationService; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; /** @@ -127,7 +129,7 @@ public class SendIssueNotificationsStep implements ComputationStep { IssueChangeNotification changeNotification = new IssueChangeNotification(); changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName()); changeNotification.setIssue(issue); - changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName()); + changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest()); getComponentKey(issue).ifPresent(c -> changeNotification.setComponent(c.getPublicKey(), c.getName())); service.deliver(changeNotification); } @@ -136,7 +138,7 @@ public class SendIssueNotificationsStep implements ComputationStep { NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics(); NewIssuesNotification notification = newIssuesNotificationFactory .newNewIssuesNotication() - .setProject(project.getPublicKey(), project.getName(), getBranchName()) + .setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest()) .setProjectVersion(project.getReportAttributes().getVersion()) .setAnalysisDate(new Date(analysisDate)) .setStatistics(project.getName(), globalStatistics) @@ -155,7 +157,7 @@ public class SendIssueNotificationsStep implements ComputationStep { .newMyNewIssuesNotification() .setAssignee(assignee); myNewIssuesNotification - .setProject(project.getPublicKey(), project.getName(), getBranchName()) + .setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest()) .setProjectVersion(project.getReportAttributes().getVersion()) .setAnalysisDate(new Date(analysisDate)) .setStatistics(project.getName(), assigneeStatistics) @@ -185,9 +187,16 @@ public class SendIssueNotificationsStep implements ComputationStep { return "Send issue notifications"; } + @CheckForNull private String getBranchName() { Branch branch = analysisMetadataHolder.getBranch(); - return branch.isMain() ? null : branch.getName(); + return branch.isMain() || branch.getType() == PULL_REQUEST ? null : branch.getName(); + } + + @CheckForNull + private String getPullRequest() { + Branch branch = analysisMetadataHolder.getBranch(); + return branch.getType() == PULL_REQUEST ? analysisMetadataHolder.getPullRequestId() : null; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java index a94371a998e..1b9fb9908fe 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java @@ -52,7 +52,7 @@ public class DuplicationsParser { this.componentDao = componentDao; } - public List<Block> parse(DbSession session, ComponentDto component, @Nullable String branch, @Nullable String duplicationsData) { + public List<Block> parse(DbSession session, ComponentDto component, @Nullable String branch, @Nullable String pullRequest, @Nullable String duplicationsData) { Map<String, ComponentDto> componentsByKey = newHashMap(); List<Block> blocks = newArrayList(); if (duplicationsData != null) { @@ -69,7 +69,7 @@ public class DuplicationsParser { String size = bCursor.getAttrValue("l"); String componentKey = bCursor.getAttrValue("r"); if (from != null && size != null && componentKey != null) { - duplications.add(createDuplication(componentsByKey, branch, from, size, componentKey, session)); + duplications.add(createDuplication(componentsByKey, branch, pullRequest, from, size, componentKey, session)); } } Collections.sort(duplications, new DuplicationComparator(component.uuid(), component.projectUuid())); @@ -83,12 +83,19 @@ public class DuplicationsParser { return blocks; } - private Duplication createDuplication(Map<String, ComponentDto> componentsByKey, @Nullable String branch, String from, String size, String componentDbKey, DbSession session) { + private Duplication createDuplication(Map<String, ComponentDto> componentsByKey, @Nullable String branch, @Nullable String pullRequest, String from, String size, + String componentDbKey, DbSession session) { String componentKey = convertToKey(componentDbKey); ComponentDto component = componentsByKey.get(componentKey); if (component == null) { - Optional<ComponentDto> componentDtoOptional = branch == null ? componentDao.selectByKey(session, componentKey) - : Optional.fromNullable(componentDao.selectByKeyAndBranch(session, componentKey, branch).orElseGet(null)); + Optional<ComponentDto> componentDtoOptional; + if (branch != null) { + componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndBranch(session, componentKey, branch).orElseGet(null)); + } else if (pullRequest != null) { + componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndPullRequest(session, componentKey, pullRequest).orElseGet(null)); + } else { + componentDtoOptional = componentDao.selectByKey(session, componentKey); + } component = componentDtoOptional.isPresent() ? componentDtoOptional.get() : null; componentsByKey.put(componentKey, component); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java index df342e5d032..1889105b1b6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java @@ -37,11 +37,15 @@ import org.sonar.server.user.UserSession; import static com.google.common.base.Preconditions.checkArgument; import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY; 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.writeProtobuf; -import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; public class ShowAction implements DuplicationsWsAction { + private static final String PARAM_KEY = "key"; + private static final String PARAM_UUID = "uuid"; + private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private final DbClient dbClient; private final DuplicationsParser parser; private final ShowResponseBuilder responseBuilder; @@ -68,21 +72,28 @@ public class ShowAction implements DuplicationsWsAction { new Change("6.5", "The fields 'uuid', 'projectUuid', 'subProjectUuid' are deprecated in the response.")); action - .createParam("key") + .createParam(PARAM_KEY) .setDescription("File key") .setExampleValue("my_project:/src/foo/Bar.php"); action - .createParam("uuid") + .createParam(PARAM_UUID) .setDeprecatedSince("6.5") .setDescription("File ID. If provided, 'key' must not be provided.") .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2"); action - .createParam("branch") + .createParam(PARAM_BRANCH) .setDescription("Branch key") .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setInternal(true) + .setSince("7.1") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -92,19 +103,22 @@ public class ShowAction implements DuplicationsWsAction { userSession.checkComponentPermission(UserRole.CODEVIEWER, component); String duplications = findDataFromComponent(dbSession, component); String branch = component.getBranch(); - List<DuplicationsParser.Block> blocks = parser.parse(dbSession, component, branch, duplications); - writeProtobuf(responseBuilder.build(dbSession, blocks, branch), request, response); + String pullRequest = component.getPullRequest(); + List<DuplicationsParser.Block> blocks = parser.parse(dbSession, component, branch, pullRequest, duplications); + writeProtobuf(responseBuilder.build(dbSession, blocks, branch, pullRequest), request, response); } } private ComponentDto loadComponent(DbSession dbSession, Request request) { - String componentUuid = request.param("uuid"); - String branch = request.param("branch"); - checkArgument(componentUuid == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", "uuid", PARAM_BRANCH); - if (branch == null) { - return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param("key"), UUID_AND_KEY); + String componentUuid = request.param(PARAM_UUID); + String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); + checkArgument(componentUuid == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_UUID, + PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param(PARAM_KEY), UUID_AND_KEY); } - return componentFinder.getByKeyAndOptionalBranch(dbSession, request.mandatoryParam("key"), branch); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, request.mandatoryParam(PARAM_KEY), branch, pullRequest); } @CheckForNull diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java index 1c56c039247..f6050bc6527 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java @@ -49,14 +49,14 @@ public class ShowResponseBuilder { this.componentDao = componentDao; } - ShowResponse build(DbSession session, List<DuplicationsParser.Block> blocks, @Nullable String branch) { + ShowResponse build(DbSession session, List<DuplicationsParser.Block> blocks, @Nullable String branch, @Nullable String pullRequest) { ShowResponse.Builder response = ShowResponse.newBuilder(); Map<String, String> refByComponentKey = newHashMap(); blocks.stream() .map(block -> toWsDuplication(block, refByComponentKey)) .forEach(response::addDuplications); - writeFiles(session, response, refByComponentKey, branch); + writeFiles(session, response, refByComponentKey, branch, pullRequest); return response.build(); } @@ -86,7 +86,8 @@ public class ShowResponseBuilder { return block; } - private static Duplications.File toWsFile(ComponentDto file, @Nullable ComponentDto project, @Nullable ComponentDto subProject, @Nullable String branch) { + private static Duplications.File toWsFile(ComponentDto file, @Nullable ComponentDto project, @Nullable ComponentDto subProject, @Nullable String branch, + @Nullable String pullRequest) { Duplications.File.Builder wsFile = Duplications.File.newBuilder(); wsFile.setKey(file.getKey()); wsFile.setUuid(file.uuid()); @@ -102,15 +103,14 @@ public class ShowResponseBuilder { wsFile.setSubProjectUuid(subProject.uuid()); wsFile.setSubProjectName(subProject.longName()); } - if (branch != null) { - wsFile.setBranch(branch); - } + setNullable(branch, wsFile::setBranch); + setNullable(pullRequest, wsFile::setPullRequest); return wsFile; }); return wsFile.build(); } - private void writeFiles(DbSession session, ShowResponse.Builder response, Map<String, String> refByComponentKey, @Nullable String branch) { + private void writeFiles(DbSession session, ShowResponse.Builder response, Map<String, String> refByComponentKey, @Nullable String branch, @Nullable String pullRequest) { Map<String, ComponentDto> projectsByUuid = newHashMap(); Map<String, ComponentDto> parentModulesByUuid = newHashMap(); Map<String, Duplications.File> filesByRef = response.getMutableFiles(); @@ -124,7 +124,7 @@ public class ShowResponseBuilder { ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session); ComponentDto parentModule = getParentProject(file.getRootUuid(), parentModulesByUuid, session); - filesByRef.put(ref, toWsFile(file, project, parentModule, branch)); + filesByRef.put(ref, toWsFile(file, project, parentModule, branch, pullRequest)); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java index 18dd1eef55a..2bc84dddb82 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java @@ -198,6 +198,7 @@ public class IssueQueryFactory { Collection<String> componentRootUuids = request.getComponentRootUuids(); Collection<String> componentRoots = request.getComponentRoots(); String branch = request.getBranch(); + String pullRequest = request.getPullRequest(); boolean effectiveOnComponentOnly = false; @@ -208,15 +209,15 @@ public class IssueQueryFactory { if (componentRootUuids != null) { allComponents.addAll(getComponentsFromUuids(session, componentRootUuids)); } else if (componentRoots != null) { - allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch)); + allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch, pullRequest)); } else if (components != null) { - allComponents.addAll(getComponentsFromKeys(session, components, branch)); + allComponents.addAll(getComponentsFromKeys(session, components, branch, pullRequest)); effectiveOnComponentOnly = true; } else if (componentUuids != null) { allComponents.addAll(getComponentsFromUuids(session, componentUuids)); effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly); } else if (componentKeys != null) { - allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch)); + allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch, pullRequest)); effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly); } @@ -229,13 +230,11 @@ public class IssueQueryFactory { .count() <= 1; } - private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly, - List<ComponentDto> components, SearchRequest request) { - + private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly, List<ComponentDto> components, SearchRequest request) { builder.onComponentOnly(onComponentOnly); if (onComponentOnly) { builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList())); - setBranch(builder, components.get(0), request.getBranch()); + setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest()); return; } @@ -246,9 +245,9 @@ public class IssueQueryFactory { if (projectUuids != null) { builder.projectUuids(projectUuids); } else if (projectKeys != null) { - List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch()); + List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch(), request.getPullRequest()); builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList())); - setBranch(builder, projects.get(0), request.getBranch()); + setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest()); } builder.moduleUuids(request.getModuleUuids()); builder.directories(request.getDirectories()); @@ -269,7 +268,7 @@ public class IssueQueryFactory { Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet()); checkArgument(qualifiers.size() == 1, "All components must have the same qualifier, found %s", String.join(",", qualifiers)); - setBranch(builder, components.get(0), request.getBranch()); + setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest()); String qualifier = qualifiers.iterator().next(); switch (qualifier) { case Qualifiers.VIEW: @@ -345,10 +344,15 @@ public class IssueQueryFactory { builder.directories(directoryPaths); } - private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch) { - List<ComponentDto> componentDtos = branch == null - ? dbClient.componentDao().selectByKeys(dbSession, componentKeys) - : dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch); + private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) { + List<ComponentDto> componentDtos; + if (branch != null) { + componentDtos = dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch); + } else if (pullRequest != null) { + componentDtos = dbClient.componentDao().selectByKeysAndPullRequest(dbSession, componentKeys, pullRequest); + } else { + componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys); + } if (!componentKeys.isEmpty() && componentDtos.isEmpty()) { return singletonList(UNKNOWN_COMPONENT); } @@ -376,8 +380,11 @@ public class IssueQueryFactory { return mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid; } - private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch) { - builder.branchUuid(branch == null ? null : component.projectUuid()); - builder.mainBranch(branch == null || component.equals(UNKNOWN_COMPONENT) || !branch.equals(component.getBranch())); + private static void setBranch(IssueQuery.Builder builder, ComponentDto component, @Nullable String branch, @Nullable String pullRequest) { + builder.branchUuid(branch == null && pullRequest == null ? null : component.projectUuid()); + builder.mainBranch(UNKNOWN_COMPONENT.equals(component) + || (branch == null && pullRequest == null) + || (branch != null && !branch.equals(component.getBranch())) + || (pullRequest != null && !pullRequest.equals(component.getPullRequest()))); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java index 83a424bcb00..8c9b7d4af11 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java @@ -48,6 +48,7 @@ public class SearchRequest { private List<String> moduleUuids; private Boolean onComponentOnly; private String branch; + private String pullRequest; private String organization; private Integer page; private Integer pageSize; @@ -453,4 +454,14 @@ public class SearchRequest { this.branch = branch; return this; } + + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public SearchRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java index 1746f383dfb..d8bfbe925e9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java @@ -54,6 +54,7 @@ public abstract class AbstractNewIssuesEmailTemplate extends EmailTemplate { static final String FIELD_PROJECT_VERSION = "projectVersion"; static final String FIELD_ASSIGNEE = "assignee"; static final String FIELD_BRANCH = "branch"; + static final String FIELD_PULL_REQUEST = "pullRequest"; protected final EmailSettings settings; protected final I18n i18n; @@ -78,12 +79,16 @@ public abstract class AbstractNewIssuesEmailTemplate extends EmailTemplate { } String projectName = checkNotNull(notification.getFieldValue(FIELD_PROJECT_NAME)); String branchName = notification.getFieldValue(FIELD_BRANCH); + String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST); StringBuilder message = new StringBuilder(); message.append("Project: ").append(projectName).append(NEW_LINE); if (branchName != null) { message.append("Branch: ").append(branchName).append(NEW_LINE); } + if (pullRequest!= null) { + message.append("Pull request: ").append(pullRequest).append(NEW_LINE); + } String version = notification.getFieldValue(FIELD_PROJECT_VERSION); if (version != null) { message.append("Version: ").append(version).append(NEW_LINE); @@ -203,6 +208,10 @@ public abstract class AbstractNewIssuesEmailTemplate extends EmailTemplate { if (branchName != null) { url += "&branch=" + encode(branchName); } + String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST); + if (pullRequest != null) { + url += "&pullRequest=" + encode(pullRequest); + } url += "&createdAt=" + encode(DateUtils.formatDateTime(date)); message .append("More details at: ") diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java index 517b0e10fb9..d25e8adb45e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java @@ -29,6 +29,11 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; import org.sonar.db.component.ComponentDto; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_KEY; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_NAME; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST; + public class IssueChangeNotification extends Notification { public static final String TYPE = "issue-changes"; @@ -54,14 +59,17 @@ public class IssueChangeNotification extends Notification { } public IssueChangeNotification setProject(ComponentDto project) { - return setProject(project.getKey(), project.name(), project.getBranch()); + return setProject(project.getKey(), project.name(), project.getBranch(), project.getPullRequest()); } - public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch) { - setFieldValue("projectName", projectName); - setFieldValue("projectKey", projectKey); + public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch, @Nullable String pullRequest) { + setFieldValue(FIELD_PROJECT_NAME, projectName); + setFieldValue(FIELD_PROJECT_KEY, projectKey); if (branch != null) { - setFieldValue("branch", branch); + setFieldValue(FIELD_BRANCH, branch); + } + if (pullRequest != null) { + setFieldValue(FIELD_PULL_REQUEST, pullRequest); } return this; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java index b6b9d239ad2..487c7fd4c2c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java @@ -33,6 +33,8 @@ import org.sonar.plugins.emailnotifications.api.EmailMessage; import org.sonar.plugins.emailnotifications.api.EmailTemplate; import static java.net.URLEncoder.encode; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST; /** * Creates email message for notification "issue-changes". @@ -99,10 +101,14 @@ public class IssueChangesEmailTemplate extends EmailTemplate { private static void appendHeader(Notification notif, StringBuilder sb) { appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey"))); - String branchName = notif.getFieldValue("branch"); + String branchName = notif.getFieldValue(FIELD_BRANCH); if (branchName != null) { appendField(sb, "Branch", null, branchName); } + String pullRequest = notif.getFieldValue(FIELD_PULL_REQUEST); + if (pullRequest != null) { + appendField(sb, "Pull request", null, pullRequest); + } appendField(sb, "Rule", null, notif.getFieldValue("ruleName")); appendField(sb, "Message", null, notif.getFieldValue("message")); } @@ -114,10 +120,14 @@ public class IssueChangesEmailTemplate extends EmailTemplate { .append("/project/issues?id=").append(encode(notification.getFieldValue("projectKey"), "UTF-8")) .append("&issues=").append(issueKey) .append("&open=").append(issueKey); - String branchName = notification.getFieldValue("branch"); + String branchName = notification.getFieldValue(FIELD_BRANCH); if (branchName != null) { sb.append("&branch=").append(branchName); } + String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST); + if (pullRequest != null) { + sb.append("&pullRequest=").append(pullRequest); + } sb.append(NEW_LINE); } catch (UnsupportedEncodingException e) { throw new IllegalStateException("Encoding not supported", e); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java index cebeeb6f2ed..ed50922ed7e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java @@ -65,10 +65,14 @@ public class MyNewIssuesEmailTemplate extends AbstractNewIssuesEmailTemplate { settings.getServerBaseURL(), encode(projectKey), encode(assignee)); - String branchName = notification.getFieldValue("branch"); + String branchName = notification.getFieldValue(FIELD_BRANCH); if (branchName != null) { url += "&branch=" + encode(branchName); } + String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST); + if (pullRequest != null) { + url += "&pullRequest=" + encode(pullRequest); + } url += "&createdAt=" + encode(DateUtils.formatDateTime(date)); message .append("More details at: ") diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java index e7e819ec86b..df8c6bb943b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java @@ -46,6 +46,7 @@ import org.sonar.server.user.index.UserIndex; import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH; import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION; +import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST; import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_DATE; import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY; import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME; @@ -79,12 +80,15 @@ public class NewIssuesNotification extends Notification { return this; } - public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName) { + public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName, @Nullable String pullRequest) { setFieldValue(FIELD_PROJECT_NAME, projectName); setFieldValue(FIELD_PROJECT_KEY, projectKey); if (branchName != null) { setFieldValue(FIELD_BRANCH, branchName); } + if (pullRequest != null) { + setFieldValue(FIELD_PULL_REQUEST, pullRequest); + } return this; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index a76a989689e..3bd129ba174 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -72,6 +72,7 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet; import static org.sonar.server.es.SearchOptions.MAX_LIMIT; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT; @@ -106,6 +107,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PLANNED; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED; @@ -319,6 +321,12 @@ public class SearchAction implements IssuesWsAction { .setInternal(true) .setSince("6.6"); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); + action.createParam(PARAM_ORGANIZATION) .setDescription("Organization key") .setRequired(false) @@ -568,6 +576,7 @@ public class SearchAction implements IssuesWsAction { .setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS)) .setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY)) .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) .setOrganization(request.param(PARAM_ORGANIZATION)) .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index e41a988935c..a59cd7f5eb2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -162,6 +162,7 @@ public class SearchResponseFormat { issueBuilder.setOrganization(data.getOrganizationKey(component.getOrganizationUuid())); issueBuilder.setComponent(component.getKey()); setNullable(component.getBranch(), issueBuilder::setBranch); + setNullable(component.getPullRequest(), issueBuilder::setPullRequest); ComponentDto project = data.getComponentByUuid(dto.getProjectUuid()); if (project != null) { issueBuilder.setProject(project.getKey()); @@ -309,6 +310,7 @@ public class SearchResponseFormat { .setLongName(nullToEmpty(dto.longName())) .setEnabled(dto.isEnabled()); setNullable(dto.getBranch(), builder::setBranch); + setNullable(dto.getPullRequest(), builder::setPullRequest); String path = dto.path(); // path is not applicable to the components that are not files. // Value must not be "" in this case. diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java index b7cf692c7e8..431821f0095 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java @@ -63,7 +63,7 @@ public class LiveQualityGateComputerImpl implements LiveQualityGateComputer { @Override public QualityGate loadQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto project, BranchDto branch) { - if (branch.getBranchType() == BranchType.SHORT) { + if (branch.getBranchType() == BranchType.SHORT || branch.getBranchType() == BranchType.PULL_REQUEST) { return ShortLivingBranchQualityGate.GATE; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java index 8ede31ea203..f6257345fad 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java @@ -58,17 +58,7 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; -import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT; -import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; -import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric; -import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods; -import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; -import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; -import static org.sonar.server.ws.WsUtils.checkRequest; -import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY; import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_COMPONENT; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS; @@ -80,6 +70,18 @@ import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_ID; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_KEY; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KEYS; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; +import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; +import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric; +import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods; +import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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 ComponentAction implements MeasuresWsAction { private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE); @@ -123,6 +125,12 @@ public class ComponentAction implements MeasuresWsAction { .setInternal(true) .setSince("6.6"); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); + createMetricKeysParameter(action); createAdditionalFieldsParameter(action); createDeveloperParameters(action); @@ -156,10 +164,14 @@ public class ComponentAction implements MeasuresWsAction { String componentKey = request.getComponent(); String componentId = request.getComponentId(); String branch = request.getBranch(); - checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH); - return branch == null - ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT) - : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); + String pullRequest = request.getPullRequest(); + checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", + DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_KEY); + } + checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } private Optional<ComponentDto> getReferenceComponent(DbSession dbSession, ComponentDto component) { @@ -251,6 +263,7 @@ public class ComponentAction implements MeasuresWsAction { .setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID)) .setComponent(request.param(PARAM_COMPONENT)) .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) .setAdditionalFields(request.paramAsStrings(PARAM_ADDITIONAL_FIELDS)) .setMetricKeys(request.mandatoryParamAsStrings(PARAM_METRIC_KEYS)); checkRequest(!componentRequest.getMetricKeys().isEmpty(), "At least one metric key must be provided"); @@ -265,6 +278,7 @@ public class ComponentAction implements MeasuresWsAction { private String componentId; private String component; private String branch; + private String pullRequest; private List<String> metricKeys; private List<String> additionalFields; private String developerId; @@ -308,6 +322,16 @@ public class ComponentAction implements MeasuresWsAction { return this; } + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public ComponentRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + private List<String> getMetricKeys() { return metricKeys; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java index a0e4839f2c6..6129f9af528 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java @@ -59,6 +59,7 @@ class ComponentDtoToWsComponent { .setName(component.name()) .setQualifier(component.qualifier()); Protobuf.setNullable(component.getBranch(), wsComponent::setBranch); + Protobuf.setNullable(component.getPullRequest(), wsComponent::setPullRequest); Protobuf.setNullable(component.path(), wsComponent::setPath); Protobuf.setNullable(component.description(), wsComponent::setDescription); Protobuf.setNullable(component.language(), wsComponent::setLanguage); diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java index d593764c2a9..5ba9c3f9228 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java @@ -84,20 +84,6 @@ import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; import static org.sonar.db.component.ComponentTreeQuery.Strategy.CHILDREN; import static org.sonar.db.component.ComponentTreeQuery.Strategy.LEAVES; import static org.sonar.server.component.ComponentFinder.ParamNames.BASE_COMPONENT_ID_AND_KEY; -import static org.sonar.server.component.ComponentFinder.ParamNames.DEVELOPER_ID_AND_KEY; -import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; -import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.updateMeasureBuilder; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters; -import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; -import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric; -import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods; -import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; -import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; -import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter; -import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext; -import static org.sonar.server.ws.WsUtils.checkRequest; -import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_COMPONENT_TREE; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS; @@ -112,8 +98,23 @@ import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KE import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_QUALIFIERS; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_STRATEGY; +import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; +import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.updateMeasureBuilder; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; +import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric; +import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods; +import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; +import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext; +import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter; +import static org.sonar.server.ws.WsUtils.checkRequest; +import static org.sonar.server.ws.WsUtils.writeProtobuf; /** * <p>Navigate through components based on different strategy with specified measures. @@ -216,6 +217,12 @@ public class ComponentTreeAction implements MeasuresWsAction { .setInternal(true) .setSince("6.6"); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); + action.createParam(PARAM_METRIC_SORT) .setDescription( format("Metric key to sort by. The '%s' parameter must contain the '%s' or '%s' value. It must be part of the '%s' parameter", Param.SORT, METRIC_SORT, METRIC_PERIOD_SORT, @@ -343,6 +350,7 @@ public class ComponentTreeAction implements MeasuresWsAction { .setBaseComponentId(request.param(DEPRECATED_PARAM_BASE_COMPONENT_ID)) .setComponent(request.param(PARAM_COMPONENT)) .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) .setMetricKeys(metricKeys) .setStrategy(request.mandatoryParam(PARAM_STRATEGY)) .setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS)) @@ -428,13 +436,17 @@ public class ComponentTreeAction implements MeasuresWsAction { } private ComponentDto loadComponent(DbSession dbSession, ComponentTreeRequest request) { - String componentKey = request.getComponent(); String componentId = request.getBaseComponentId(); + String componentKey = request.getComponent(); String branch = request.getBranch(); - checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", DEPRECATED_PARAM_BASE_COMPONENT_ID, PARAM_BRANCH); - return branch == null - ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, BASE_COMPONENT_ID_AND_KEY) - : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); + String pullRequest = request.getPullRequest(); + checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", + DEPRECATED_PARAM_BASE_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, BASE_COMPONENT_ID_AND_KEY); + } + checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } private Map<String, ComponentDto> searchReferenceComponentsById(DbSession dbSession, List<ComponentDto> components) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java index 6791e894cdc..31b2525c06e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java @@ -28,6 +28,7 @@ class ComponentTreeRequest { private String baseComponentId; private String component; private String branch; + private String pullRequest; private String strategy; private List<String> qualifiers; private List<String> additionalFields; @@ -82,6 +83,16 @@ class ComponentTreeRequest { } @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public ComponentTreeRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + @CheckForNull public String getStrategy() { return strategy; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java index df6766e218f..4f3af4cbbc0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; +import javax.annotation.CheckForNull; +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; @@ -46,22 +48,21 @@ import org.sonar.server.user.UserSession; import org.sonar.server.ws.KeyExamples; import org.sonarqube.ws.Measures.SearchHistoryResponse; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - import static java.lang.String.format; import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime; import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED; -import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; -import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_SEARCH_HISTORY; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_FROM; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRICS; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_TO; +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.writeProtobuf; public class SearchHistoryAction implements MeasuresWsAction { @@ -78,18 +79,6 @@ public class SearchHistoryAction implements MeasuresWsAction { this.userSession = userSession; } - private static SearchHistoryRequest toWsRequest(Request request) { - return SearchHistoryRequest.builder() - .setComponent(request.mandatoryParam(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)) - .setMetrics(request.mandatoryParamAsStrings(PARAM_METRICS)) - .setFrom(request.param(PARAM_FROM)) - .setTo(request.param(PARAM_TO)) - .setPage(request.mandatoryParamAsInt(Param.PAGE)) - .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) - .build(); - } - @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(ACTION_SEARCH_HISTORY) @@ -112,6 +101,12 @@ public class SearchHistoryAction implements MeasuresWsAction { .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setSince("7.1") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); + action.createParam(PARAM_METRICS) .setDescription("Comma-separated list of metric keys") .setRequired(true) @@ -141,6 +136,19 @@ public class SearchHistoryAction implements MeasuresWsAction { writeProtobuf(searchHistoryResponse, request, response); } + private static SearchHistoryRequest toWsRequest(Request request) { + return SearchHistoryRequest.builder() + .setComponent(request.mandatoryParam(PARAM_COMPONENT)) + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) + .setMetrics(request.mandatoryParamAsStrings(PARAM_METRICS)) + .setFrom(request.param(PARAM_FROM)) + .setTo(request.param(PARAM_TO)) + .setPage(request.mandatoryParamAsInt(Param.PAGE)) + .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) + .build(); + } + private Function<SearchHistoryRequest, SearchHistoryResult> search() { return request -> { try (DbSession dbSession = dbClient.openSession(false)) { @@ -199,15 +207,14 @@ public class SearchHistoryAction implements MeasuresWsAction { private ComponentDto loadComponent(DbSession dbSession, SearchHistoryRequest request) { String componentKey = request.getComponent(); String branch = request.getBranch(); - if (branch != null) { - return componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); - } - return componentFinder.getByKey(dbSession, componentKey); + String pullRequest = request.getPullRequest(); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } static class SearchHistoryRequest { private final String component; private final String branch; + private final String pullRequest; private final List<String> metrics; private final String from; private final String to; @@ -217,6 +224,7 @@ public class SearchHistoryAction implements MeasuresWsAction { public SearchHistoryRequest(Builder builder) { this.component = builder.component; this.branch = builder.branch; + this.pullRequest = builder.pullRequest; this.metrics = builder.metrics; this.from = builder.from; this.to = builder.to; @@ -233,6 +241,11 @@ public class SearchHistoryAction implements MeasuresWsAction { return branch; } + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + public List<String> getMetrics() { return metrics; } @@ -263,6 +276,7 @@ public class SearchHistoryAction implements MeasuresWsAction { static class Builder { private String component; private String branch; + private String pullRequest; private List<String> metrics; private String from; private String to; @@ -283,6 +297,11 @@ public class SearchHistoryAction implements MeasuresWsAction { return this; } + public Builder setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + public Builder setMetrics(List<String> metrics) { this.metrics = metrics; return this; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 7de860aff00..21d6a286cb1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -38,6 +38,7 @@ import org.sonar.server.authentication.LogOAuthWarning; import org.sonar.server.badge.ws.ProjectBadgesWsModule; import org.sonar.server.batch.BatchWsModule; import org.sonar.server.branch.BranchFeatureProxyImpl; +import org.sonar.server.branch.pr.ws.PullRequestWsModule; import org.sonar.server.branch.ws.BranchWsModule; import org.sonar.server.ce.ws.CeWsModule; import org.sonar.server.component.ComponentCleanerService; @@ -401,6 +402,7 @@ public class PlatformLevel4 extends PlatformLevel { // components BranchWsModule.class, + PullRequestWsModule.class, ProjectsWsModule.class, ProjectsEsModule.class, ProjectTagsWsModule.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java index 2eb9f12db75..a2c56bd7fbd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java @@ -28,6 +28,7 @@ public class ProjectAnalysesWsParameters { public static final String PARAM_FROM = "from"; public static final String PARAM_TO = "to"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; private ProjectAnalysesWsParameters() { // static access only diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java index c6ace6ced7f..49105d01b43 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java @@ -47,15 +47,17 @@ import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE; import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC; -import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; -import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonar.server.projectanalysis.ws.EventCategory.OTHER; import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH; import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_CATEGORY; import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_FROM; import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PROJECT; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_TO; import static org.sonar.server.projectanalysis.ws.SearchRequest.DEFAULT_PAGE_SIZE; +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.writeProtobuf; public class SearchAction implements ProjectAnalysesWsAction { private static final Set<String> ALLOWED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW); @@ -92,6 +94,12 @@ public class SearchAction implements ProjectAnalysesWsAction { .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setSince("7.1") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); + action.createParam(PARAM_CATEGORY) .setDescription("Event category. Filter analyses that have at least one event of the category specified.") .setPossibleValues(EnumSet.allOf(EventCategory.class)) @@ -123,6 +131,7 @@ public class SearchAction implements ProjectAnalysesWsAction { return SearchRequest.builder() .setProject(request.mandatoryParam(PARAM_PROJECT)) .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)) .setCategory(category == null ? null : EventCategory.valueOf(category)) .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) @@ -170,10 +179,8 @@ public class SearchAction implements ProjectAnalysesWsAction { private ComponentDto loadComponent(DbSession dbSession, SearchRequest request) { String project = request.getProject(); String branch = request.getBranch(); - if (branch != null) { - return componentFinder.getByKeyAndBranch(dbSession, project, branch); - } - return componentFinder.getByKey(dbSession, project); + String pullRequest = request.getPullRequest(); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, project, branch, pullRequest); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java index 83c118c5356..be9a5eade9a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java @@ -31,6 +31,7 @@ class SearchRequest { private final String project; private final String branch; + private final String pullRequest; private final EventCategory category; private final int page; private final int pageSize; @@ -39,7 +40,8 @@ class SearchRequest { private SearchRequest(Builder builder) { this.project = builder.project; - this.branch= builder.branch; + this.branch = builder.branch; + this.pullRequest = builder.pullRequest; this.category = builder.category; this.page = builder.page; this.pageSize = builder.pageSize; @@ -57,6 +59,11 @@ class SearchRequest { } @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + @CheckForNull public EventCategory getCategory() { return category; } @@ -86,6 +93,7 @@ class SearchRequest { public static class Builder { private String project; private String branch; + private String pullRequest; private EventCategory category; private int page = 1; private int pageSize = DEFAULT_PAGE_SIZE; @@ -106,6 +114,11 @@ class SearchRequest { return this; } + public Builder setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + public Builder setCategory(@Nullable EventCategory category) { this.category = category; return this; diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java index b086408aa92..801dd6ffa73 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java @@ -28,7 +28,7 @@ import org.sonar.api.measures.CoreMetrics; import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN; /** - * Offers constants describing the Hardcoded Quality Gate for short living branches. + * Offers constants describing the Hardcoded Quality Gate for short living branches and pull requests. */ public final class ShortLivingBranchQualityGate { public static final long ID = -1_963_456_987L; diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java index 88fa20eb41a..7f7ce140c69 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java @@ -40,6 +40,12 @@ public interface QGChangeEventListener { } enum Status { - OPEN, CONFIRMED, REOPENED, RESOLVED, CLOSED + OPEN, + CONFIRMED, + REOPENED, + RESOLVED_FP, + RESOLVED_WF, + RESOLVED_FIXED } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java index b2fe05c836f..d46a318820f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java @@ -23,7 +23,9 @@ import com.google.common.collect.Multimap; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -95,17 +97,47 @@ public class QGChangeEventListenersImpl implements QGChangeEventListeners { } } - private static class ChangedIssueImpl implements ChangedIssue { + static class ChangedIssueImpl implements ChangedIssue { private final String key; private final QGChangeEventListener.Status status; private final RuleType type; private ChangedIssueImpl(DefaultIssue issue) { this.key = issue.key(); - this.status = QGChangeEventListener.Status.valueOf(issue.getStatus()); + this.status = statusOf(issue); this.type = issue.type(); } + static QGChangeEventListener.Status statusOf(DefaultIssue issue) { + switch (issue.status()) { + case Issue.STATUS_OPEN: + return QGChangeEventListener.Status.OPEN; + case Issue.STATUS_CONFIRMED: + return QGChangeEventListener.Status.CONFIRMED; + case Issue.STATUS_REOPENED: + return QGChangeEventListener.Status.REOPENED; + case Issue.STATUS_RESOLVED: + return statusOfResolved(issue); + default: + throw new IllegalStateException("Unexpected status: " + issue.status()); + } + } + + private static QGChangeEventListener.Status statusOfResolved(DefaultIssue issue) { + String resolution = issue.resolution(); + Objects.requireNonNull(resolution, "A resolved issue should have a resolution"); + switch (resolution) { + case Issue.RESOLUTION_FALSE_POSITIVE: + return QGChangeEventListener.Status.RESOLVED_FP; + case Issue.RESOLUTION_WONT_FIX: + return QGChangeEventListener.Status.RESOLVED_WF; + case Issue.RESOLUTION_FIXED: + return QGChangeEventListener.Status.RESOLVED_FIXED; + default: + throw new IllegalStateException("Unexpected resolution for a resolved issue: " + resolution); + } + } + @Override public String getKey() { return key; diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ListDefinitionsAction.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ListDefinitionsAction.java index 5118f8368d9..c7ed814898f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ListDefinitionsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ListDefinitionsAction.java @@ -21,6 +21,8 @@ package org.sonar.server.setting.ws; import java.util.List; import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.PropertyFieldDefinition; @@ -42,6 +44,7 @@ import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.server.setting.ws.SettingsWs.SETTING_ON_BRANCHES; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT; +import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.writeProtobuf; @@ -81,6 +84,7 @@ public class ListDefinitionsAction implements SettingsWsAction { .setDescription("Component key") .setExampleValue(KEY_PROJECT_EXAMPLE_001); settingsWsSupport.addBranchParam(action); + settingsWsSupport.addPullRequestParam(action); } @Override @@ -107,7 +111,8 @@ public class ListDefinitionsAction implements SettingsWsAction { private static ListDefinitionsRequest toWsRequest(Request request) { return new ListDefinitionsRequest() .setComponent(request.param(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)); + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)); } private static Optional<String> getQualifier(Optional<ComponentDto> component) { @@ -120,7 +125,7 @@ public class ListDefinitionsAction implements SettingsWsAction { if (componentKey == null) { return Optional.empty(); } - ComponentDto component = componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch()); + ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest()); userSession.checkComponentPermission(USER, component); return Optional.of(component); } @@ -164,23 +169,36 @@ public class ListDefinitionsAction implements SettingsWsAction { private String branch; private String component; + private String pullRequest; - public ListDefinitionsRequest setBranch(String branch) { + public ListDefinitionsRequest setComponent(@Nullable String component) { + this.component = component; + return this; + } + + @CheckForNull + public String getComponent() { + return component; + } + + public ListDefinitionsRequest setBranch(@Nullable String branch) { this.branch = branch; return this; } + @CheckForNull public String getBranch() { return branch; } - public ListDefinitionsRequest setComponent(String component) { - this.component = component; + public ListDefinitionsRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; return this; } - public String getComponent() { - return component; + @CheckForNull + public String getPullRequest() { + return pullRequest; } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ResetAction.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ResetAction.java index 3c35e729f02..8a65b530f88 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ResetAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ResetAction.java @@ -23,6 +23,8 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.server.ws.Change; @@ -42,8 +44,10 @@ import static java.util.Collections.emptyList; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEYS; +import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; public class ResetAction implements SettingsWsAction { @@ -92,6 +96,11 @@ public class ResetAction implements SettingsWsAction { .setExampleValue(KEY_BRANCH_EXAMPLE_001) .setInternal(true) .setSince("6.6"); + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); } @Override @@ -129,9 +138,10 @@ public class ResetAction implements SettingsWsAction { private static ResetRequest toWsRequest(Request request) { return new ResetRequest() - .setKeys(request.paramAsStrings(PARAM_KEYS)) + .setKeys(request.mandatoryParamAsStrings(PARAM_KEYS)) .setComponent(request.param(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)); + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)); } private Optional<ComponentDto> getComponent(DbSession dbSession, ResetRequest request) { @@ -139,7 +149,7 @@ public class ResetAction implements SettingsWsAction { if (componentKey == null) { return Optional.empty(); } - return Optional.of(componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch())); + return Optional.of(componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest())); } private void checkPermissions(Optional<ComponentDto> component) { @@ -153,23 +163,36 @@ public class ResetAction implements SettingsWsAction { private static class ResetRequest { private String branch; + private String pullRequest; private String component; private List<String> keys; - public ResetRequest setBranch(String branch) { + public ResetRequest setBranch(@Nullable String branch) { this.branch = branch; return this; } + @CheckForNull public String getBranch() { return branch; } - public ResetRequest setComponent(String component) { + public ResetRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public ResetRequest setComponent(@Nullable String component) { this.component = component; return this; } + @CheckForNull public String getComponent() { return component; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SetAction.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SetAction.java index ed4a9943cd6..2113ff2070f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SetAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SetAction.java @@ -34,6 +34,7 @@ import java.util.Set; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.PropertyType; @@ -63,6 +64,7 @@ import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_FIELD_VALUES; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEY; +import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUE; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUES; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; @@ -133,7 +135,9 @@ public class SetAction implements SettingsWsAction { .setDescription("Component key") .setDeprecatedKey("componentKey", "6.3") .setExampleValue(KEY_PROJECT_EXAMPLE_001); + settingsWsSupport.addBranchParam(action); + settingsWsSupport.addPullRequestParam(action); } @Override @@ -293,7 +297,8 @@ public class SetAction implements SettingsWsAction { .setValues(request.multiParam(PARAM_VALUES)) .setFieldValues(request.multiParam(PARAM_FIELD_VALUES)) .setComponent(request.param(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)); + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)); checkArgument(isNotEmpty(set.getKey()), "Setting key is mandatory and must not be empty"); checkArgument(set.getValues() != null, "Setting values must not be null"); checkArgument(set.getFieldValues() != null, "Setting fields values must not be null"); @@ -316,7 +321,7 @@ public class SetAction implements SettingsWsAction { if (componentKey == null) { return Optional.empty(); } - return Optional.of(componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch())); + return Optional.of(componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest())); } private PropertyDto toProperty(SetRequest request, Optional<ComponentDto> component) { @@ -351,26 +356,39 @@ public class SetAction implements SettingsWsAction { private static class SetRequest { private String branch; + private String pullRequest; private String component; private List<String> fieldValues; private String key; private String value; private List<String> values; - public SetRequest setBranch(String branch) { + public SetRequest setBranch(@Nullable String branch) { this.branch = branch; return this; } + @CheckForNull public String getBranch() { return branch; } - public SetRequest setComponent(String component) { + public SetRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public SetRequest setComponent(@Nullable String component) { this.component = component; return this; } + @CheckForNull public String getComponent() { return component; } @@ -393,16 +411,17 @@ public class SetAction implements SettingsWsAction { return key; } - public SetRequest setValue(String value) { + public SetRequest setValue(@Nullable String value) { this.value = value; return this; } + @CheckForNull public String getValue() { return value; } - public SetRequest setValues(List<String> values) { + public SetRequest setValues(@Nullable List<String> values) { this.values = values; return this; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsParameters.java index 61cdd2b3c0f..d46cb8d0c1c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsParameters.java @@ -43,6 +43,7 @@ public class SettingsWsParameters { public static final String PARAM_COMPONENT = "component"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; public static final String PARAM_KEYS = "keys"; public static final String PARAM_KEY = "key"; public static final String PARAM_VALUE = "value"; diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsSupport.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsSupport.java index c53bf41b24b..ab3d550195a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsSupport.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/SettingsWsSupport.java @@ -39,7 +39,9 @@ import static org.sonar.api.PropertyType.LICENSE; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH; +import static org.sonar.server.setting.ws.SettingsWsParameters.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; @ServerSide public class SettingsWsSupport { @@ -108,4 +110,12 @@ public class SettingsWsSupport { .setInternal(true) .setSince("6.6"); } + + WebService.NewParam addPullRequestParam(WebService.NewAction action) { + return action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request. Only available on following settings : %s", SettingsWs.SETTING_ON_BRANCHES.stream().collect(COMMA_JOINER)) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true) + .setSince("7.1"); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ValuesAction.java b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ValuesAction.java index 57fefaeb3a0..ec2d3a0ee23 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ValuesAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/setting/ws/ValuesAction.java @@ -30,6 +30,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.server.ws.Change; @@ -56,8 +58,8 @@ import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT; import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEYS; +import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; -import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.writeProtobuf; @@ -108,11 +110,8 @@ public class ValuesAction implements SettingsWsAction { action.createParam(PARAM_COMPONENT) .setDescription("Component key") .setExampleValue(KEY_PROJECT_EXAMPLE_001); - action.createParam(PARAM_BRANCH) - .setDescription("Branch key") - .setExampleValue(KEY_BRANCH_EXAMPLE_001) - .setInternal(true) - .setSince("6.6"); + settingsWsSupport.addBranchParam(action); + settingsWsSupport.addPullRequestParam(action); } @Override @@ -136,7 +135,8 @@ public class ValuesAction implements SettingsWsAction { private static ValuesRequest toWsRequest(Request request) { ValuesRequest result = new ValuesRequest() .setComponent(request.param(PARAM_COMPONENT)) - .setBranch(request.param(PARAM_BRANCH)); + .setBranch(request.param(PARAM_BRANCH)) + .setPullRequest(request.param(PARAM_PULL_REQUEST)); if (request.hasParam(PARAM_KEYS)) { result.setKeys(request.paramAsStrings(PARAM_KEYS)); } @@ -154,11 +154,11 @@ public class ValuesAction implements SettingsWsAction { if (componentKey == null) { return Optional.empty(); } - ComponentDto component = componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, valuesRequest.getBranch()); + ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, valuesRequest.getBranch(), valuesRequest.getPullRequest()); if (!userSession.hasComponentPermission(USER, component) && - !userSession.hasComponentPermission(SCAN_EXECUTION, component) && - !userSession.hasPermission(OrganizationPermission.SCAN, component.getOrganizationUuid())) { - throw insufficientPrivilegesException(); + !userSession.hasComponentPermission(SCAN_EXECUTION, component) && + !userSession.hasPermission(OrganizationPermission.SCAN, component.getOrganizationUuid())) { + throw insufficientPrivilegesException(); } return Optional.of(component); } @@ -303,32 +303,46 @@ public class ValuesAction implements SettingsWsAction { private static class ValuesRequest { private String branch; + private String pullRequest; private String component; private List<String> keys; - public ValuesRequest setBranch(String branch) { + public ValuesRequest setBranch(@Nullable String branch) { this.branch = branch; return this; } + @CheckForNull public String getBranch() { return branch; } - public ValuesRequest setComponent(String component) { + public ValuesRequest setPullRequest(@Nullable String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + @CheckForNull + public String getPullRequest() { + return pullRequest; + } + + public ValuesRequest setComponent(@Nullable String component) { this.component = component; return this; } + @CheckForNull public String getComponent() { return component; } - public ValuesRequest setKeys(List<String> keys) { + public ValuesRequest setKeys(@Nullable List<String> keys) { this.keys = keys; return this; } + @CheckForNull public List<String> getKeys() { return keys; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java index 334a71e9212..263f4c7539e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/LinesAction.java @@ -43,7 +43,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY; 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.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; +import static org.sonar.server.ws.WsUtils.checkRequest; public class LinesAction implements SourcesWsAction { @@ -52,6 +54,7 @@ public class LinesAction implements SourcesWsAction { private static final String PARAM_FROM = "from"; private static final String PARAM_TO = "to"; private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private final ComponentFinder componentFinder; private final SourceService sourceService; @@ -91,7 +94,7 @@ public class LinesAction implements SourcesWsAction { "has been renamed \"lineHits\", \"conditions\" and \"coveredConditions\""), new Change("6.2", "fields \"itLineHits\", \"itConditions\" and \"itCoveredConditions\" " + "are no more returned"), - new Change("6.6", "fields \"branch\" added")) + new Change("6.6", "field \"branch\" added")) .setHandler(this); action @@ -111,6 +114,12 @@ public class LinesAction implements SourcesWsAction { .setExampleValue(KEY_BRANCH_EXAMPLE_001); action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); + + action .createParam(PARAM_FROM) .setDescription("First line to return. Starts from 1") .setExampleValue("10") @@ -146,11 +155,15 @@ public class LinesAction implements SourcesWsAction { String componentKey = wsRequest.param(PARAM_KEY); String componentId = wsRequest.param(PARAM_UUID); String branch = wsRequest.param(PARAM_BRANCH); - checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_UUID, - PARAM_BRANCH); - return branch == null - ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, UUID_AND_KEY) - : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch); + String pullRequest = wsRequest.param(PARAM_PULL_REQUEST); + checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", + PARAM_UUID, PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { + return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, UUID_AND_KEY); + } + + checkRequest(componentKey!=null, "The '%s' parameter is missing", PARAM_KEY); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } private void writeSource(Iterable<DbFileSources.Line> lines, JsonWriter json) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/ws/RawAction.java b/server/sonar-server/src/main/java/org/sonar/server/source/ws/RawAction.java index 34a391c9a7d..0591acd2a45 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/source/ws/RawAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/source/ws/RawAction.java @@ -36,9 +36,13 @@ import org.sonar.server.source.SourceService; import org.sonar.server.user.UserSession; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; public class RawAction implements SourcesWsAction { + private static final String PARAM_KEY = "key"; + private static final String PARAM_BRANCH = "branch"; + private static final String PARAM_PULL_REQUEST = "pullRequest"; private final DbClient dbClient; private final SourceService sourceService; private final UserSession userSession; @@ -60,24 +64,31 @@ public class RawAction implements SourcesWsAction { .setHandler(this); action - .createParam("key") + .createParam(PARAM_KEY) .setRequired(true) .setDescription("File key") .setExampleValue("my_project:src/foo/Bar.php"); action - .createParam("branch") + .createParam(PARAM_BRANCH) .setDescription("Branch key") .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + action + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override public void handle(Request request, Response response) { - String fileKey = request.mandatoryParam("key"); - String branch = request.param("branch"); + String fileKey = request.mandatoryParam(PARAM_KEY); + String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); try (DbSession dbSession = dbClient.openSession(false)) { - ComponentDto file = componentFinder.getByKeyAndOptionalBranch(dbSession, fileKey, branch); + ComponentDto file = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, fileKey, branch, pullRequest); userSession.checkComponentPermission(UserRole.CODEVIEWER, file); Optional<Iterable<String>> lines = sourceService.getLinesAsRawText(dbSession, file.uuid(), 1, Integer.MAX_VALUE); diff --git a/server/sonar-server/src/main/java/org/sonar/server/test/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/test/ws/ListAction.java index 27f733238f9..ab1110af674 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/test/ws/ListAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/test/ws/ListAction.java @@ -54,6 +54,7 @@ import static org.sonar.api.web.UserRole.CODEVIEWER; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.server.es.SearchOptions.MAX_LIMIT; 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.checkFoundWithOptional; public class ListAction implements TestsWsAction { @@ -64,6 +65,7 @@ public class ListAction implements TestsWsAction { public static final String SOURCE_FILE_KEY = "sourceFileKey"; public static final String SOURCE_FILE_LINE_NUMBER = "sourceFileLineNumber"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; private final DbClient dbClient; private final TestIndex testIndex; @@ -98,6 +100,7 @@ public class ListAction implements TestsWsAction { .setDeprecatedSince("5.6") .setHandler(this) .setChangelog(new Change("6.6", "\"fileBranch\" field is now returned")) + .setChangelog(new Change("7.1", "\"filePullRequest\" field is now returned")) .addPagingParams(100, MAX_LIMIT); action @@ -136,6 +139,12 @@ public class ListAction implements TestsWsAction { .setSince("6.6") .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setSince("7.1") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -146,6 +155,7 @@ public class ListAction implements TestsWsAction { String sourceFileUuid = request.param(SOURCE_FILE_ID); String sourceFileKey = request.param(SOURCE_FILE_KEY); String branch = request.param(PARAM_BRANCH); + String pullRequest = request.param(PARAM_PULL_REQUEST); Integer sourceFileLineNumber = request.paramAsInt(SOURCE_FILE_LINE_NUMBER); SearchOptions searchOptions = new SearchOptions().setPage( request.mandatoryParamAsInt(PAGE), @@ -154,7 +164,7 @@ public class ListAction implements TestsWsAction { SearchResult<TestDoc> tests; Map<String, ComponentDto> componentsByTestFileUuid; try (DbSession dbSession = dbClient.openSession(false)) { - tests = searchTests(dbSession, testUuid, testFileUuid, testFileKey, sourceFileUuid, sourceFileKey, branch, sourceFileLineNumber, searchOptions); + tests = searchTests(dbSession, testUuid, testFileUuid, testFileKey, sourceFileUuid, sourceFileKey, branch, pullRequest, sourceFileLineNumber, searchOptions); componentsByTestFileUuid = buildComponentsByTestFileUuid(dbSession, tests.getDocs()); } @@ -175,6 +185,7 @@ public class ListAction implements TestsWsAction { testBuilder.setFileKey(component.getKey()); testBuilder.setFileName(component.longName()); setNullable(component.getBranch(), testBuilder::setFileBranch); + setNullable(component.getPullRequest(), testBuilder::setFilePullRequest); } testBuilder.setStatus(Tests.TestStatus.valueOf(testDoc.status())); if (testDoc.durationInMs() != null) { @@ -209,7 +220,8 @@ public class ListAction implements TestsWsAction { } private SearchResult<TestDoc> searchTests(DbSession dbSession, @Nullable String testUuid, @Nullable String testFileUuid, @Nullable String testFileKey, - @Nullable String sourceFileUuid, @Nullable String sourceFileKey, @Nullable String branch, @Nullable Integer sourceFileLineNumber, SearchOptions searchOptions) { + @Nullable String sourceFileUuid, @Nullable String sourceFileKey, @Nullable String branch, @Nullable String pullRequest, + @Nullable Integer sourceFileLineNumber, SearchOptions searchOptions) { if (testUuid != null) { TestDoc testDoc = checkFoundWithOptional(testIndex.getNullableByTestUuid(testUuid), "Test with id '%s' is not found", testUuid); checkComponentUuidPermission(dbSession, testDoc.fileUuid()); @@ -220,7 +232,7 @@ public class ListAction implements TestsWsAction { return testIndex.searchByTestFileUuid(testFileUuid, searchOptions); } if (testFileKey != null) { - ComponentDto testFile = componentFinder.getByKeyAndOptionalBranch(dbSession, testFileKey, branch); + ComponentDto testFile = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, testFileKey, branch, pullRequest); userSession.checkComponentPermission(CODEVIEWER, testFile); return testIndex.searchByTestFileUuid(testFile.uuid(), searchOptions); } @@ -230,7 +242,7 @@ public class ListAction implements TestsWsAction { return testIndex.searchBySourceFileUuidAndLineNumber(sourceFile.uuid(), sourceFileLineNumber, searchOptions); } if (sourceFileKey != null && sourceFileLineNumber != null) { - ComponentDto sourceFile = componentFinder.getByKeyAndOptionalBranch(dbSession, sourceFileKey, branch); + ComponentDto sourceFile = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, sourceFileKey, branch, pullRequest); userSession.checkComponentPermission(CODEVIEWER, sourceFile); return testIndex.searchBySourceFileUuidAndLineNumber(sourceFile.uuid(), sourceFileLineNumber, searchOptions); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java index 580a62cd94a..f0c22150fe7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java @@ -68,11 +68,13 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_ import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; public class ComponentAction implements NavigationWsAction { static final String PARAM_COMPONENT = "component"; static final String PARAM_BRANCH = "branch"; + static final String PARAM_PULL_REQUEST = "pullRequest"; private static final String PROPERTY_CONFIGURABLE = "configurable"; private static final String PROPERTY_HAS_ROLE_POLICY = "hasRolePolicy"; @@ -124,6 +126,12 @@ public class ComponentAction implements NavigationWsAction { .setDescription("Branch key") .setInternal(true) .setExampleValue(KEY_BRANCH_EXAMPLE_001); + + projectNavigation + .createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setInternal(true) + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001); } @Override @@ -131,7 +139,8 @@ public class ComponentAction implements NavigationWsAction { String componentKey = request.mandatoryParam(PARAM_COMPONENT); try (DbSession session = dbClient.openSession(false)) { String branch = request.param(PARAM_BRANCH); - ComponentDto component = componentFinder.getByKeyAndOptionalBranch(session, componentKey, branch); + String pullRequest = request.param(PARAM_PULL_REQUEST); + ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(session, componentKey, branch, pullRequest); if (!userSession.hasComponentPermission(USER, component) && !userSession.hasComponentPermission(ADMIN, component) && !userSession.isSystemAdministrator()) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java index c4ab3e786ff..96f91e244ae 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java @@ -80,6 +80,7 @@ public class CreateAction implements UsersWsAction { new Change("6.3", "The password is only mandatory when creating local users, and should not be set on non local users"), new Change("6.3", "The 'infos' message is no more returned when a user is reactivated")) .setPost(true) + .setResponseExample(getClass().getResource("create-example.json")) .setHandler(this); action.createParam(PARAM_LOGIN) @@ -104,13 +105,13 @@ public class CreateAction implements UsersWsAction { .setExampleValue("myname@email.com"); action.createParam(PARAM_SCM_ACCOUNTS) - .setDescription("This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT) + .setDescription("Comma-separated list of SCM accounts. This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT) .setDeprecatedKey(PARAM_SCM_ACCOUNTS_DEPRECATED, "6.0") .setDeprecatedSince("6.1") .setExampleValue("myscmaccount1,myscmaccount2"); action.createParam(PARAM_SCM_ACCOUNT) - .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.") + .setDescription("List of SCM accounts. To set several values, the parameter must be called once for each value.") .setExampleValue("scmAccount=firstValue&scmAccount=secondValue&scmAccount=thirdValue"); action.createParam(PARAM_LOCAL) diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetHomepageAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetHomepageAction.java index d9fb7b1fd8e..92bebf6eb56 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetHomepageAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetHomepageAction.java @@ -122,7 +122,7 @@ public class SetHomepageAction implements UsersWsAction { switch (type) { case PROJECT: checkArgument(isNotBlank(componentParameter), PARAMETER_REQUIRED, type.name(), PARAM_COMPONENT); - return componentFinder.getByKeyAndOptionalBranch(dbSession, componentParameter, branchParameter).uuid(); + return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentParameter, branchParameter, null).uuid(); case PORTFOLIO: case APPLICATION: checkArgument(isNotBlank(componentParameter), PARAMETER_REQUIRED, type.name(), PARAM_COMPONENT); diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java index c17c7de7f86..2d62e584785 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java @@ -49,7 +49,7 @@ public final class Branch { } public enum Type { - LONG, SHORT + LONG, SHORT, PULL_REQUEST } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java index 96c24d254fb..39f6d102c5b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java @@ -123,10 +123,16 @@ public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory { } return format("%s/dashboard?branch=%s&id=%s", server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); - } else { + } + if (branch.getType() == Branch.Type.SHORT) { return format("%s/project/issues?branch=%s&id=%s&resolved=false", server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); } + if (branch.getType() == Branch.Type.PULL_REQUEST) { + return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false", + server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); + } + return projectUrlOf(project); } private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java index 2afa9edbc14..dea020eec4d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java @@ -30,6 +30,7 @@ public class KeyExamples { public static final String KEY_ORG_EXAMPLE_002 = "foo-company"; public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch"; + public static final String KEY_PULL_REQUEST_EXAMPLE_001 = "5461"; public static final String NAME_WEBHOOK_EXAMPLE_001 = "My Webhook"; public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar"; diff --git a/server/sonar-server/src/main/resources/org/sonar/server/branch/pr/ws/list-example.json b/server/sonar-server/src/main/resources/org/sonar/server/branch/pr/ws/list-example.json new file mode 100644 index 00000000000..a925c1f901a --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/branch/pr/ws/list-example.json @@ -0,0 +1,17 @@ +{ + "pullRequests": [ + { + "key": "123", + "title": "Add feature X", + "branch": "feature/bar", + "base": "feature/foo", + "status": { + "bugs": 0, + "vulnerabilities": 0, + "codeSmells": 0 + }, + "analysisDate": "2017-04-01T02:15:42+0200", + "url": "https://github.com/SonarSource/sonar-core-plugins/pull/32" + } + ] +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/create-example.json b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/create-example.json new file mode 100644 index 00000000000..b4bdfda4fe5 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/create-example.json @@ -0,0 +1,10 @@ +{ + "user": { + "login": "ada.lovelace", + "name": "Ada Lovelace", + "email": "ada.lovelace@noteg.com", + "scmAccounts": ["ada.lovelace"], + "active": true, + "local": true + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java index 0f7a82d7c52..034441c567d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java @@ -286,6 +286,7 @@ public class MeasureActionTest { .containsExactlyInAnyOrder( tuple("project", true), tuple("branch", false), + tuple("pullRequest", false), tuple("metric", true)); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java index 56da90807b7..2071e6c2c6f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java @@ -189,7 +189,8 @@ public class QualityGateActionTest { .extracting(Param::key, Param::isRequired) .containsExactlyInAnyOrder( tuple("project", true), - tuple("branch", false)); + tuple("branch", false), + tuple("pullRequest", false)); } private MetricDto createQualityGateMetric() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/DeleteActionTest.java new file mode 100644 index 00000000000..1301ef7692b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/DeleteActionTest.java @@ -0,0 +1,147 @@ +/* + * 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.branch.pr.ws; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentCleanerService; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.component.TestComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.sonar.db.component.BranchType.PULL_REQUEST; + +public class DeleteActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class); + private ComponentFinder componentFinder = TestComponentFinder.from(db); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + public WsActionTester ws = new WsActionTester(new DeleteAction(db.getDbClient(), componentFinder, userSession, componentCleanerService)); + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.key()).isEqualTo("delete"); + assertThat(definition.isPost()).isTrue(); + assertThat(definition.isInternal()).isFalse(); + assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project", "pullRequest"); + assertThat(definition.since()).isEqualTo("7.1"); + } + + @Test + public void delete_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("1984").setBranchType(PULL_REQUEST)); + + userSession.logIn().addProjectPermission(UserRole.ADMIN, project); + + ws.newRequest() + .setParam("project", project.getKey()) + .setParam("pullRequest", "1984") + .execute(); + verifyDeletedKey(branch.getDbKey()); + } + + @Test + public void fail_if_missing_project_parameter() { + userSession.logIn(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'project' parameter is missing"); + + ws.newRequest().execute(); + } + + @Test + public void fail_if_missing_pull_request_parameter() { + userSession.logIn(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'pullRequest' parameter is missing"); + + ws.newRequest().setParam("project", "projectName").execute(); + } + + @Test + public void fail_if_not_logged_in() { + expectedException.expect(UnauthorizedException.class); + expectedException.expectMessage("Authentication is required"); + + ws.newRequest().execute(); + } + + @Test + public void fail_if_pull_request_does_not_exist() { + ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("orwell")); + userSession.logIn().addProjectPermission(UserRole.ADMIN, project); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Pull request '1984' is not found for project 'orwell'"); + + ws.newRequest() + .setParam("project", project.getDbKey()) + .setParam("pullRequest", "1984") + .execute(); + } + + @Test + public void fail_if_project_does_not_exist() { + userSession.logIn(); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Project key 'foo' not found"); + + ws.newRequest() + .setParam("project", "foo") + .setParam("pullRequest", "123") + .execute(); + } + + private void verifyDeletedKey(String key) { + ArgumentCaptor<ComponentDto> argument = ArgumentCaptor.forClass(ComponentDto.class); + verify(componentCleanerService).deleteBranch(any(DbSession.class), argument.capture()); + assertThat(argument.getValue().getDbKey()).isEqualTo(key); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/ListActionTest.java new file mode 100644 index 00000000000..e8715b90bae --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/ListActionTest.java @@ -0,0 +1,372 @@ +/* + * 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.branch.pr.ws; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.DateUtils; +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.ComponentTesting; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.component.SnapshotTesting; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.es.EsTester; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexDefinition; +import org.sonar.server.issue.index.IssueIndexer; +import org.sonar.server.issue.index.IssueIteratorFactory; +import org.sonar.server.permission.index.AuthorizationTypeSupport; +import org.sonar.server.permission.index.PermissionIndexerTester; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.ProjectPullRequests.ListWsResponse; +import org.sonarqube.ws.ProjectPullRequests.PullRequest; + +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; +import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.rules.RuleType.BUG; +import static org.sonar.api.rules.RuleType.CODE_SMELL; +import static org.sonar.api.rules.RuleType.VULNERABILITY; +import static org.sonar.api.utils.DateUtils.dateToLong; +import static org.sonar.api.utils.DateUtils.parseDateTime; +import static org.sonar.db.component.BranchType.LONG; +import static org.sonar.db.component.BranchType.PULL_REQUEST; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.ProjectPullRequests.Status; + +public class ListActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public EsTester es = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig())); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT); + private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient())); + private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession)); + private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer); + + public WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), userSession, new ComponentFinder(db.getDbClient(), resourceTypes), issueIndex)); + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + assertThat(definition.key()).isEqualTo("list"); + assertThat(definition.isPost()).isFalse(); + assertThat(definition.isInternal()).isFalse(); + assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project"); + assertThat(definition.since()).isEqualTo("7.1"); + } + + @Test + public void json_example() { + ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("sonarqube")); + ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo").setBranchType(LONG)); + ComponentDto pullRequest = db.components().insertProjectBranch(project, + b -> b.setKey("123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(longLivingBranch.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder() + .setBranch("feature/bar") + .setTitle("Add feature X") + .setUrl("https://github.com/SonarSource/sonar-core-plugins/pull/32") + .build())); + userSession.logIn().addProjectPermission(UserRole.USER, project); + + db.getDbClient().snapshotDao().insert(db.getSession(), + SnapshotTesting.newAnalysis(pullRequest).setLast(true).setCreatedAt(DateUtils.parseDateTime("2017-04-01T01:15:42+0100").getTime())); + db.commit(); + + String json = ws.newRequest() + .setParam("project", project.getDbKey()) + .execute() + .getInput(); + + assertJson(json).isSimilarTo(ws.getDef().responseExampleAsString()); + } + + @Test + public void pull_request() { + ComponentDto project = db.components().insertMainBranch(); + db.components().insertProjectBranch(project, + b -> b.setKey("123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(project.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + userSession.logIn().addProjectPermission(UserRole.USER, project); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getDbKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList()) + .extracting(PullRequest::getKey, PullRequest::getBranch, PullRequest::getIsOrphan) + .containsExactlyInAnyOrder(tuple("123", "feature/bar", false)); + } + + @Test + public void project_with_zero_branches() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + + String json = ws.newRequest() + .setParam("project", project.getDbKey()) + .setMediaType(MediaTypes.JSON) + .execute() + .getInput(); + + assertJson(json).isSimilarTo("{\"pullRequests\": []}"); + } + + @Test + public void pull_requests() { + 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 pullRequestOnLong = db.components().insertProjectBranch(project, + b -> b.setKey("pull_request_on_long") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(longLivingBranch.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + ComponentDto pullRequestOnMaster = db.components().insertProjectBranch(project, + b -> b.setKey("pull_request_on_master") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(project.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList()) + .extracting(PullRequest::getKey, PullRequest::getBase) + .containsExactlyInAnyOrder( + tuple(pullRequestOnLong.getPullRequest(), longLivingBranch.getBranch()), + tuple(pullRequestOnMaster.getPullRequest(), "master")); + } + + @Test + public void base_branch_is_using_default_main_name_when_main_branch_has_no_name() { + ComponentDto project = db.components().insertMainBranch(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto pullRequest = db.components().insertProjectBranch(project, + b -> b.setKey("pr-123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(project.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder() + .setBranch("feature123").build())); + + ListWsResponse response = ws.newRequest() + .setParam("project", pullRequest.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequests(0)) + .extracting(PullRequest::getKey, PullRequest::getBase) + .containsExactlyInAnyOrder(pullRequest.getPullRequest(), "master"); + } + + @Test + public void pull_request_on_removed_branch() { + ComponentDto project = db.components().insertMainBranch(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto pullRequest = db.components().insertProjectBranch(project, + b -> b.setKey("pr-123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid("unknown") + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList()) + .extracting(PullRequest::getKey, PullRequest::hasBase, PullRequest::getIsOrphan) + .containsExactlyInAnyOrder( + tuple(pullRequest.getPullRequest(), false, true)); + } + + @Test + public void status_on_pull_requests() { + ComponentDto project = db.components().insertMainBranch(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG)); + ComponentDto pullRequest = db.components().insertProjectBranch(project, + b -> b.setKey("pr-123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(longLivingBranch.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + RuleDefinitionDto rule = db.rules().insert(); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(BUG).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(BUG).setResolution(RESOLUTION_FIXED)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(VULNERABILITY).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(VULNERABILITY).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null)); + db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(RESOLUTION_FALSE_POSITIVE)); + issueIndexer.indexOnStartup(emptySet()); + permissionIndexerTester.allowOnlyAnyone(project); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList().stream().map(PullRequest::getStatus)) + .extracting(Status::hasBugs, Status::getBugs, Status::hasVulnerabilities, Status::getVulnerabilities, Status::hasCodeSmells, Status::getCodeSmells) + .containsExactlyInAnyOrder(tuple(true, 1L, true, 2L, true, 3L)); + } + + @Test + public void status_on_pull_request_with_no_issue() { + ComponentDto project = db.components().insertMainBranch(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG)); + db.components().insertProjectBranch(project, + b -> b.setKey("pr-123") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(longLivingBranch.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build())); + issueIndexer.indexOnStartup(emptySet()); + permissionIndexerTester.allowOnlyAnyone(project); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList().stream().map(PullRequest::getStatus)) + .extracting(Status::getBugs, Status::getVulnerabilities, Status::getCodeSmells) + .containsExactlyInAnyOrder(tuple(0L, 0L, 0L)); + } + + @Test + public void response_contains_date_of_last_analysis() { + Long lastAnalysisLongLivingBranch = dateToLong(parseDateTime("2017-04-01T00:00:00+0100")); + Long previousAnalysisPullRequest = dateToLong(parseDateTime("2017-04-02T00:00:00+0100")); + Long lastAnalysisPullRequest = dateToLong(parseDateTime("2017-04-03T00:00:00+0100")); + + ComponentDto project = db.components().insertMainBranch(); + userSession.logIn().addProjectPermission(UserRole.USER, project); + + ComponentDto pullRequest1 = db.components().insertProjectBranch(project, + b -> b.setKey("pr1") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(project.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/pr1").build())); + + ComponentDto longLivingBranch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG)); + + ComponentDto pullRequest2 = db.components().insertProjectBranch(project, + b -> b.setKey("pr2") + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(longLivingBranch2.uuid()) + .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/pr2").build())); + + db.getDbClient().snapshotDao().insert(db.getSession(), + SnapshotTesting.newAnalysis(longLivingBranch2).setCreatedAt(lastAnalysisLongLivingBranch)); + db.getDbClient().snapshotDao().insert(db.getSession(), + SnapshotTesting.newAnalysis(pullRequest2).setCreatedAt(previousAnalysisPullRequest).setLast(false)); + db.getDbClient().snapshotDao().insert(db.getSession(), + SnapshotTesting.newAnalysis(pullRequest2).setCreatedAt(lastAnalysisPullRequest)); + db.commit(); + issueIndexer.indexOnStartup(emptySet()); + permissionIndexerTester.allowOnlyAnyone(project); + + ListWsResponse response = ws.newRequest() + .setParam("project", project.getKey()) + .executeProtobuf(ListWsResponse.class); + + assertThat(response.getPullRequestsList()) + .extracting(PullRequest::hasAnalysisDate, b -> "".equals(b.getAnalysisDate()) ? null : dateToLong(parseDateTime(b.getAnalysisDate()))) + .containsExactlyInAnyOrder( + tuple(false, null), + tuple(true, lastAnalysisPullRequest)); + } + + @Test + public void fail_when_using_branch_db_key() throws Exception { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertMainBranch(organization); + userSession.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey())); + + ws.newRequest() + .setParam("project", branch.getDbKey()) + .execute(); + } + + @Test + public void fail_if_missing_project_parameter() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The 'project' parameter is missing"); + + ws.newRequest().execute(); + } + + @Test + public void fail_if_not_a_reference_on_project() { + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project)); + userSession.logIn().addProjectPermission(UserRole.USER, project); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid project key"); + + ws.newRequest() + .setParam("project", file.getDbKey()) + .execute(); + } + + @Test + public void fail_if_project_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Component key 'foo' not found"); + + ws.newRequest() + .setParam("project", "foo") + .execute(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestWsModuleTest.java new file mode 100644 index 00000000000..1db6e4b2c44 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestWsModuleTest.java @@ -0,0 +1,35 @@ +/* + * 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.branch.pr.ws; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; + +public class PullRequestWsModuleTest { + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + new PullRequestWsModule().configure(container); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsParametersTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsParametersTest.java new file mode 100644 index 00000000000..03054ca4fcb --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsParametersTest.java @@ -0,0 +1,33 @@ +/* + * 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.branch.pr.ws; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.test.TestUtils.hasOnlyPrivateConstructors; + +public class PullRequestsWsParametersTest { + + @Test + public void private_method() { + assertThat(hasOnlyPrivateConstructors(PullRequestsWsParameters.class)).isTrue(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsTest.java new file mode 100644 index 00000000000..59268f2136b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/pr/ws/PullRequestsWsTest.java @@ -0,0 +1,51 @@ +/* + * 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.branch.pr.ws; + +import org.junit.Test; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PullRequestsWsTest { + + @Test + public void define_ws() { + PullRequestsWs underTest = new PullRequestsWs(new PullRequestWsAction() { + @Override + public void define(WebService.NewController context) { + context.createAction("foo").setHandler(this); + } + + @Override + public void handle(Request request, Response response) { + + } + }); + + WebService.Context context = new WebService.Context(); + underTest.define(context); + + assertThat(context.controller("api/project_pull_requests").action("foo")).isNotNull(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java index 937466ea214..c3a1608dbeb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java @@ -70,7 +70,7 @@ import static org.sonar.api.utils.DateUtils.dateToLong; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; import static org.sonar.test.JsonAssert.assertJson; -import static org.sonarqube.ws.ProjectBranches.Branch.Status; +import static org.sonarqube.ws.ProjectBranches.Status; public class ListActionTest { diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java index e3b7bad2c69..a55739065f3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java @@ -19,7 +19,6 @@ */ package org.sonar.server.ce.ws; -import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.List; @@ -38,6 +37,7 @@ import org.sonar.db.ce.CeActivityDto.Status; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskTypes; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; import org.sonar.db.organization.OrganizationDto; @@ -51,10 +51,10 @@ import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; import org.sonar.test.JsonAssert; import org.sonarqube.ws.Ce; -import org.sonarqube.ws.Common; -import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.Ce.ActivityResponse; import org.sonarqube.ws.Ce.Task; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.MediaTypes; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -68,6 +68,7 @@ import static org.sonar.db.ce.CeQueueDto.Status.IN_PROGRESS; import static org.sonar.db.ce.CeQueueDto.Status.PENDING; import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_KEY; import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_TYPE_KEY; +import static org.sonar.db.ce.CeTaskCharacteristicDto.PULL_REQUEST; import static org.sonar.db.component.BranchType.LONG; import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_ID; import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_QUERY; @@ -382,6 +383,45 @@ public class ActivityActionTest { } @Test + public void pull_request_in_past_activity() { + logInAsSystemAdministrator(); + ComponentDto project = db.components().insertMainBranch(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(pullRequest); + CeActivityDto activity = insertActivity("T1", project, SUCCESS, analysis); + insertCharacteristic(activity, PULL_REQUEST, pullRequest.getPullRequest()); + + ActivityResponse response = ws.newRequest().executeProtobuf(ActivityResponse.class); + + assertThat(response.getTasksList()) + .extracting(Task::getId, Ce.Task::getPullRequest, Ce.Task::hasPullRequestTitle, Ce.Task::getStatus, Ce.Task::getComponentKey) + .containsExactlyInAnyOrder( + // TODO the pull request title must be loaded from db + tuple("T1", pullRequest.getPullRequest(), false, Ce.TaskStatus.SUCCESS, pullRequest.getKey())); + } + + @Test + public void pull_request_in_queue_analysis() { + logInAsSystemAdministrator(); + String branch = "pr-123"; + CeQueueDto queue1 = insertQueue("T1", null, IN_PROGRESS); + insertCharacteristic(queue1, PULL_REQUEST, branch); + CeQueueDto queue2 = insertQueue("T2", null, PENDING); + insertCharacteristic(queue2, PULL_REQUEST, branch); + + ActivityResponse response = ws.newRequest() + .setParam("status", "FAILED,IN_PROGRESS,PENDING") + .executeProtobuf(ActivityResponse.class); + + assertThat(response.getTasksList()) + .extracting(Task::getId, Ce.Task::getPullRequest, Ce.Task::hasPullRequestTitle, Ce.Task::getStatus) + .containsExactlyInAnyOrder( + tuple("T1", branch, false, Ce.TaskStatus.IN_PROGRESS), + tuple("T2", branch, false, Ce.TaskStatus.PENDING)); + } + + @Test public void fail_if_both_filters_on_component_id_and_name() { expectedException.expect(BadRequestException.class); expectedException.expectMessage("componentId and componentQuery must not be set at the same time"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentFinderTest.java index 6a7c2ec0d78..fc4869d7280 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentFinderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentFinderTest.java @@ -30,6 +30,7 @@ import org.sonar.server.exceptions.NotFoundException; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -193,6 +194,31 @@ public class ComponentFinderTest { } @Test + public void get_by_key_and_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST).setMergeBranchUuid(project.uuid())); + ComponentDto module = db.components().insertComponent(newModuleDto(branch)); + ComponentDto directory = db.components().insertComponent(newDirectory(module, "scr")); + ComponentDto file = db.components().insertComponent(newFileDto(module)); + + assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, project.getKey(), null, "pr-123").uuid()).isEqualTo(branch.uuid()); + assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, module.getKey(), null, "pr-123").uuid()).isEqualTo(module.uuid()); + assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, file.getKey(), null, "pr-123").uuid()).isEqualTo(file.uuid()); + assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, directory.getKey(), null, "pr-123").uuid()).isEqualTo(directory.uuid()); + } + + @Test + public void fail_when_pull_request_branch_provided() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Either branch or pull request can be provided, not both"); + + assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, project.getKey(), "pr-123", "pr-123").uuid()).isEqualTo(pullRequest.uuid()); + } + + @Test public void get_by_key_and_branch_accept_main_branch() { ComponentDto project = db.components().insertMainBranch(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java index 3abc3b46b23..2bbebd58d5d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java @@ -40,6 +40,7 @@ import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY; import static org.sonar.api.measures.CoreMetrics.TESTS_KEY; import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -299,7 +300,7 @@ public class AppActionTest { ComponentDto file = db.components().insertComponent(newFileDto(branch)); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time"); + expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'"); ws.newRequest() .setParam("uuid", file.uuid()) @@ -308,6 +309,21 @@ public class AppActionTest { } @Test + public void fail_if_both_componentId_and_pull_request_parameters_provided() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'"); + + ws.newRequest() + .setParam("uuid", file.uuid()) + .setParam("pullRequest", file.getPullRequest()) + .execute(); + } + + @Test public void fail_when_component_not_found() { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); @@ -352,7 +368,7 @@ public class AppActionTest { assertThat(action.isInternal()).isTrue(); assertThat(action.isPost()).isFalse(); assertThat(action.handler()).isNotNull(); - assertThat(action.params()).hasSize(3); + assertThat(action.params()).hasSize(4); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java index b7324a8974f..b03eae689f2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ShowActionTest.java @@ -19,7 +19,6 @@ */ package org.sonar.server.component.ws; -import java.io.IOException; import java.util.Date; import javax.annotation.Nullable; import org.junit.Rule; @@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -56,6 +56,7 @@ import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST; public class ShowActionTest { @Rule @@ -81,7 +82,7 @@ public class ShowActionTest { tuple("6.5", "Leak period date is added to the response"), tuple("6.6", "'branch' is added to the response"), tuple("6.6", "'version' is added to the response")); - assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("component", "componentId", "branch"); + assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "pullRequest"); WebService.Param componentId = action.param(PARAM_COMPONENT_ID); assertThat(componentId.isRequired()).isFalse(); @@ -102,6 +103,11 @@ public class ShowActionTest { assertThat(branch.isInternal()).isTrue(); assertThat(branch.isRequired()).isFalse(); assertThat(branch.since()).isEqualTo("6.6"); + + WebService.Param pullRequest = action.param(PARAM_PULL_REQUEST); + assertThat(pullRequest.isInternal()).isTrue(); + assertThat(pullRequest.isRequired()).isFalse(); + assertThat(pullRequest.since()).isEqualTo("7.1"); } @Test @@ -307,6 +313,32 @@ public class ShowActionTest { } @Test + public void pull_request() { + ComponentDto project = db.components().insertMainBranch(); + userSession.addProjectPermission(UserRole.USER, project); + String pullRequest = "pr-1234"; + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(pullRequest).setBranchType(PULL_REQUEST)); + ComponentDto module = db.components().insertComponent(newModuleDto(branch)); + ComponentDto directory = db.components().insertComponent(newDirectory(module, "dir")); + ComponentDto file = db.components().insertComponent(newFileDto(directory)); + db.components().insertSnapshot(branch, s -> s.setVersion("1.1")); + + ShowWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, pullRequest) + .executeProtobuf(ShowWsResponse.class); + + assertThat(response.getComponent()) + .extracting(Component::getKey, Component::getPullRequest, Component::getVersion) + .containsExactlyInAnyOrder(file.getKey(), pullRequest, "1.1"); + assertThat(response.getAncestorsList()).extracting(Component::getKey, Component::getPullRequest, Component::getVersion) + .containsExactlyInAnyOrder( + tuple(directory.getKey(), pullRequest, "1.1"), + tuple(module.getKey(), pullRequest, "1.1"), + tuple(branch.getKey(), pullRequest, "1.1")); + } + + @Test public void throw_ForbiddenException_if_user_doesnt_have_browse_permission_on_project() { userSession.logIn(); @@ -343,7 +375,7 @@ public class ShowActionTest { ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch")); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time"); + expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'"); ws.newRequest() .setParam(PARAM_COMPONENT_ID, branch.uuid()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java index 713908c8f66..4452efaa621 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java @@ -61,6 +61,7 @@ import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.resources.Qualifiers.FILE; import static org.sonar.api.resources.Qualifiers.PROJECT; import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newChildComponent; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -71,6 +72,7 @@ import static org.sonar.db.component.ComponentTesting.newView; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; +import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY; @@ -99,7 +101,7 @@ public class TreeActionTest { assertThat(action.responseExample()).isNotNull(); assertThat(action.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder( tuple("6.4", "The field 'id' is deprecated in the response")); - assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "qualifiers", "strategy", + assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "pullRequest", "qualifiers", "strategy", "q", "s", "p", "asc", "ps"); Param componentId = action.param(PARAM_COMPONENT_ID); @@ -339,6 +341,29 @@ public class TreeActionTest { } @Test + public void pull_request() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + String pullRequestId = "pr-123"; + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(pullRequestId).setBranchType(PULL_REQUEST)); + ComponentDto module = db.components().insertComponent(newModuleDto(branch)); + ComponentDto directory = db.components().insertComponent(newDirectory(module, "dir")); + ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(directory)); + + TreeWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, module.getKey()) + .setParam(PARAM_PULL_REQUEST, pullRequestId) + .executeProtobuf(TreeWsResponse.class); + + assertThat(response.getBaseComponent()).extracting(Components.Component::getKey, Components.Component::getPullRequest) + .containsExactlyInAnyOrder(module.getKey(), pullRequestId); + assertThat(response.getComponentsList()).extracting(Components.Component::getKey, Components.Component::getPullRequest) + .containsExactlyInAnyOrder( + tuple(directory.getKey(), pullRequestId), + tuple(file.getKey(), pullRequestId)); + } + + @Test public void fail_when_using_branch_db_key() { ComponentDto project = db.components().insertMainBranch(); userSession.addProjectPermission(UserRole.USER, project); @@ -468,7 +493,7 @@ public class TreeActionTest { ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch")); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time"); + expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'"); ws.newRequest() .setParam(PARAM_COMPONENT_ID, branch.uuid()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java index cb603a7a98d..4a112bbb4de 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImplTest.java @@ -242,6 +242,34 @@ public class AnalysisMetadataHolderImplTest { } @Test + public void setPullRequestId() { + AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(); + + String pullRequestId = "pr-123"; + underTest.setPullRequestId(pullRequestId); + + assertThat(underTest.getPullRequestId()).isEqualTo(pullRequestId); + } + + @Test + public void getPullRequestId_throws_ISE_when_holder_is_not_initialized() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Pull request id has not been set"); + + new AnalysisMetadataHolderImpl().getPullRequestId(); + } + + @Test + public void setPullRequestId_throws_ISE_when_called_twice() { + AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(); + underTest.setPullRequestId("pr-123"); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Pull request id has already been set"); + underTest.setPullRequestId("pr-234"); + } + + @Test public void set_and_get_project() { AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(); @@ -315,4 +343,15 @@ public class AnalysisMetadataHolderImplTest { assertThat(underTest.isShortLivingBranch()).isTrue(); } + + @Test + public void getPullRequestBranch_returns_true() { + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + + AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl(); + underTest.setBranch(branch); + + assertThat(underTest.isPullRequest()).isTrue(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java index cf89ba5747c..08f75f27e95 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java @@ -49,6 +49,8 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Muta private final InitializedProperty<Branch> branch = new InitializedProperty<>(); + private final InitializedProperty<String> pullRequestId = new InitializedProperty<>(); + private final InitializedProperty<Project> project = new InitializedProperty<>(); private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); @@ -168,6 +170,18 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Muta } @Override + public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) { + this.pullRequestId.setProperty(pullRequestId); + return this; + } + + @Override + public String getPullRequestId() { + checkState(pullRequestId.isInitialized(), "Pull request id has not been set"); + return pullRequestId.getProperty(); + } + + @Override public AnalysisMetadataHolderRule setProject(Project p) { this.project.setProperty(p); return this; @@ -226,4 +240,10 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Muta Branch property = this.branch.getProperty(); return property != null && property.getType() == BranchType.LONG; } + + @Override + public boolean isPullRequest() { + Branch property = this.branch.getProperty(); + return property != null && property.getType() == BranchType.PULL_REQUEST; + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java index 9935ae33d25..24a8f1d15fd 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java @@ -121,6 +121,17 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen } @Override + public String getPullRequestId() { + return delegate.getPullRequestId(); + } + + @Override + public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) { + delegate.setPullRequestId(pullRequestId); + return this; + } + + @Override public MutableAnalysisMetadataHolderRule setProject(@Nullable Project project) { delegate.setProject(project); return this; @@ -173,4 +184,9 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen public boolean isLongLivingBranch() { return delegate.isLongLivingBranch(); } + + @Override + public boolean isPullRequest() { + return delegate.isPullRequest(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java index 15e0705c3e4..4270765b208 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java @@ -302,6 +302,11 @@ public class PostProjectAnalysisTasksExecutorTest { } @Override + public String getPullRequestId() { + throw new UnsupportedOperationException(); + } + + @Override public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) { throw new UnsupportedOperationException(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImplTest.java index ad333dd598d..f37bf46dd46 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImplTest.java @@ -81,7 +81,28 @@ public class BranchPersisterImplTest { assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2); assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1); + } + + @Test + public void persist_pull_request_data() { + String pullRequestId = "pr-123"; + analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, pullRequestId)); + analysisMetadataHolder.setPullRequestId(pullRequestId); + treeRootHolder.setRoot(BRANCH); + + // add main branch in project table and in metadata + ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey()); + analysisMetadataHolder.setProject(Project.copyOf(dto)); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto); + // this should add new columns in project and project_branches + underTest.persist(dbTester.getSession()); + + dbTester.getSession().commit(); + + assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2); + assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1); + assertThat(dbTester.countSql("select count(*) from project_branches where pull_request_binary is not null")).isEqualTo(1); } private static Branch createBranch(BranchType type, boolean isMain, String name) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java index 492b0ec5eb1..f0fca60160f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java @@ -151,6 +151,25 @@ public class ShortBranchComponentsWithIssuesTest { } @Test + public void should_find_components_with_issues_to_merge_on_derived_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + setRootUuid(project.uuid()); + + ComponentDto pullRequest = db.components().insertProjectBranch(project, + b -> b.setBranchType(BranchType.PULL_REQUEST), + b -> b.setMergeBranchUuid(project.uuid())); + + RuleDefinitionDto rule = db.rules().insert(); + + ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null)); + db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssue).setStatus("RESOLVED")); + + underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient()); + + assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1); + } + + @Test public void should_not_find_components_with_issues_to_merge_on_derived_long() { ComponentDto project = db.components().insertMainBranch(); setRootUuid(project.uuid()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java index b98b719412e..69d84ffe759 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegatorTest.java @@ -101,4 +101,18 @@ public class IssueTrackingDelegatorTest { verifyZeroInteractions(tracker); verifyZeroInteractions(mergeBranchTracker); } + + @Test + public void delegate_pull_request_tracker() { + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + when(analysisMetadataHolder.getBranch()).thenReturn(mock(Branch.class)); + when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(true); + + underTest.track(component); + + verify(shortBranchTracker).track(component); + verifyZeroInteractions(tracker); + verifyZeroInteractions(mergeBranchTracker); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java index 3d61a0369bb..43cdc901f2a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java @@ -68,6 +68,17 @@ public class LoadQualityGateStepTest { } @Test + public void add_hardcoded_QG_on_pull_request() { + when(analysisMetadataHolder.isPullRequest()).thenReturn(true); + QualityGate qualityGate = mock(QualityGate.class); + when(qualityGateService.findById(ShortLivingBranchQualityGate.ID)).thenReturn(Optional.of(qualityGate)); + + underTest.execute(); + + assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(qualityGate); + } + + @Test public void execute_sets_default_QualityGate_when_project_has_no_settings() { when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().asConfig()); QualityGate defaultGate = mock(QualityGate.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStepTest.java index 1bf8cb09521..0501ef46791 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStepTest.java @@ -325,4 +325,23 @@ public class QualityGateEventsStepTest { verifyZeroInteractions(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService); } + + @Test + public void no_alert_on_pull_request_branches() { + Branch shortBranch = mock(Branch.class); + when(shortBranch.getType()).thenReturn(BranchType.PULL_REQUEST); + analysisMetadataHolder.setBranch(shortBranch); + TreeRootHolder treeRootHolder = mock(TreeRootHolder.class); + MetricRepository metricRepository = mock(MetricRepository.class); + MeasureRepository measureRepository = mock(MeasureRepository.class); + EventRepository eventRepository = mock(EventRepository.class); + NotificationService notificationService = mock(NotificationService.class); + + QualityGateEventsStep underTest = new QualityGateEventsStep(treeRootHolder, metricRepository, measureRepository, + eventRepository, notificationService, analysisMetadataHolder); + + underTest.execute(); + + verifyZeroInteractions(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java index 5dddeaf6033..75a68862c44 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java @@ -944,6 +944,11 @@ public class ReportPersistComponentsStepTest extends BaseStepTest { } @Override + public String getPullRequestId() { + throw new UnsupportedOperationException(); + } + + @Override public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) { String moduleKey = module.getKey(); if (fileOrDir == null || isEmpty(fileOrDir.getPath())) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java index 4778aa7a0f2..eacd042c8ca 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java @@ -67,6 +67,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newBranchDto; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; @@ -79,6 +80,7 @@ import static org.sonar.server.computation.task.projectanalysis.component.Report public class SendIssueNotificationsStepTest extends BaseStepTest { private static final String BRANCH_NAME = "feature"; + private static final String PULL_REQUEST_ID = "pr-123"; private static final long ANALYSE_DATE = 123L; private static final int FIVE_MINUTES_IN_MS = 1000 * 60 * 5; @@ -143,7 +145,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { underTest.execute(); verify(notificationService).deliver(newIssuesNotificationMock); - verify(newIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null); + verify(newIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null, null); verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE)); verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any()); verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION); @@ -206,7 +208,25 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { underTest.execute(); verify(notificationService).deliver(newIssuesNotificationMock); - verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME); + verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME, null); + verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE)); + verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class)); + verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION); + } + + @Test + public void send_global_new_issues_notification_on_pull_request() { + ComponentDto branch = setUpProjectWithBranch(); + issueCache.newAppender().append( + new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close(); + when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), SendIssueNotificationsStep.NOTIF_TYPES)).thenReturn(true); + analysisMetadataHolder.setBranch(newPullRequest()); + analysisMetadataHolder.setPullRequestId(PULL_REQUEST_ID); + + underTest.execute(); + + verify(notificationService).deliver(newIssuesNotificationMock); + verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), null, PULL_REQUEST_ID); verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE)); verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class)); verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION); @@ -238,7 +258,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { verify(notificationService).deliver(newIssuesNotificationMock); verify(notificationService).deliver(myNewIssuesNotificationMock); verify(myNewIssuesNotificationMock).setAssignee(ISSUE_ASSIGNEE); - verify(myNewIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null); + verify(myNewIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null, null); verify(myNewIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE)); verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any(NewIssuesStatistics.Stats.class)); verify(myNewIssuesNotificationMock).setDebt(ISSUE_DURATION); @@ -424,7 +444,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { private NewIssuesNotification createNewIssuesNotificationMock() { NewIssuesNotification notification = mock(NewIssuesNotification.class); - when(notification.setProject(any(), any(), any())).thenReturn(notification); + when(notification.setProject(any(), any(), any(), any())).thenReturn(notification); when(notification.setProjectVersion(any())).thenReturn(notification); when(notification.setAnalysisDate(any())).thenReturn(notification); when(notification.setStatistics(any(), any())).thenReturn(notification); @@ -435,7 +455,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { private MyNewIssuesNotification createMyNewIssuesNotificationMock() { MyNewIssuesNotification notification = mock(MyNewIssuesNotification.class); when(notification.setAssignee(any())).thenReturn(notification); - when(notification.setProject(any(), any(), any())).thenReturn(notification); + when(notification.setProject(any(), any(), any(), any())).thenReturn(notification); when(notification.setProjectVersion(any())).thenReturn(notification); when(notification.setAnalysisDate(any())).thenReturn(notification); when(notification.setStatistics(any(), any())).thenReturn(notification); @@ -450,6 +470,15 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { return branch; } + private static Branch newPullRequest() { + Branch branch = mock(Branch.class); + when(branch.isMain()).thenReturn(false); + when(branch.getType()).thenReturn(PULL_REQUEST); + when(branch.getName()).thenReturn(BRANCH_NAME); + when(branch.getPullRequestId()).thenReturn(PULL_REQUEST_ID); + return branch; + } + private ComponentDto setUpProjectWithBranch() { ComponentDto project = newPrivateProjectDto(newOrganizationDto()); ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java index 3764f6ca825..d45ce9934fc 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java @@ -19,13 +19,13 @@ */ package org.sonar.server.duplication.ws; -import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import java.util.List; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import static java.lang.String.format; @@ -44,14 +44,14 @@ public class DuplicationsParserTest { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); - assertThat(parser.parse(db.getSession(), file, null, null)).isEmpty(); + assertThat(parser.parse(db.getSession(), file, null, null, null)).isEmpty(); } @Test public void duplication_on_same_file() { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, null, format("<duplications>\n" + " <g>\n" + " <b s=\"31\" l=\"5\" r=\"%s\"/>\n" + @@ -80,7 +80,7 @@ public class DuplicationsParserTest { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file1 = db.components().insertComponent(newFileDto(project)); ComponentDto file2 = db.components().insertComponent(newFileDto(project)); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null, format("<duplications>\n" + " <g>\n" + " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" + @@ -111,7 +111,7 @@ public class DuplicationsParserTest { ComponentDto file2 = db.components().insertComponent(newFileDto(project1)); ComponentDto project2 = db.components().insertPrivateProject(); ComponentDto fileOnProject2 = db.components().insertComponent(newFileDto(project2)); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null, format("<duplications>\n" + " <g>\n" + " <b s=\"148\" l=\"24\" r=\"%s\"/>\n" + @@ -154,7 +154,7 @@ public class DuplicationsParserTest { ComponentDto file2 = db.components().insertComponent(newFileDto(project2) .setDbKey("com.sonarsource.orchestrator:sonar-orchestrator:src/main/java/com/sonar/orchestrator/util/CommandExecutor.java") .setLongName("CommandExecutor")); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null, format("<duplications>\n" + " <g>\n" + " <b s=\"94\" l=\"101\" r=\"%s\"/>\n" + @@ -180,7 +180,7 @@ public class DuplicationsParserTest { public void duplication_on_not_existing_file() { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, null, format("<duplications>\n" + " <g>\n" + " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" + @@ -243,7 +243,39 @@ public class DuplicationsParserTest { ComponentDto branch = db.components().insertProjectBranch(project); ComponentDto file1 = db.components().insertComponent(newFileDto(branch)); ComponentDto file2 = db.components().insertComponent(newFileDto(branch)); - List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, branch.getBranch(), + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, branch.getBranch(), null, + format("<duplications>\n" + + " <g>\n" + + " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" + + " <b s=\"31\" l=\"5\" r=\"%s\"/>\n" + + " </g>\n" + + "</duplications>", file2.getDbKey(), file1.getDbKey())); + assertThat(blocks).hasSize(1); + + List<DuplicationsParser.Duplication> duplications = blocks.get(0).getDuplications(); + assertThat(duplications).hasSize(2); + + // Current file comes first + DuplicationsParser.Duplication duplication1 = duplications.get(0); + assertThat(duplication1.file()).isEqualTo(file1); + assertThat(duplication1.file().getKey()).isEqualTo(file1.getKey()); + assertThat(duplication1.from()).isEqualTo(31); + assertThat(duplication1.size()).isEqualTo(5); + + DuplicationsParser.Duplication duplication2 = duplications.get(1); + assertThat(duplication2.file()).isEqualTo(file2); + assertThat(duplication2.file().getKey()).isEqualTo(file2.getKey()); + assertThat(duplication2.from()).isEqualTo(20); + assertThat(duplication2.size()).isEqualTo(5); + } + + @Test + public void duplication_on_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST)); + ComponentDto file1 = db.components().insertComponent(newFileDto(pullRequest)); + ComponentDto file2 = db.components().insertComponent(newFileDto(pullRequest)); + List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, pullRequest.getPullRequest(), format("<duplications>\n" + " <g>\n" + " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" + @@ -270,12 +302,8 @@ public class DuplicationsParserTest { } private static DuplicationsParser.Duplication duplication(List<DuplicationsParser.Duplication> duplications, @Nullable final String componentKey) { - return Iterables.find(duplications, new Predicate<DuplicationsParser.Duplication>() { - @Override - public boolean apply(@Nullable DuplicationsParser.Duplication input) { - return input != null && (componentKey == null ? input.file() == null : input.file() != null && componentKey.equals(input.file().getDbKey())); - } - }); + return Iterables.find(duplications, input -> input != null && (componentKey == null ? input.file() == null + : input.file() != null && componentKey.equals(input.file().getDbKey()))); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java index df9cb7591f2..488fccfe260 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowActionTest.java @@ -28,6 +28,7 @@ import org.sonar.api.measures.CoreMetrics; import org.sonar.api.server.ws.WebService; 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.metric.MetricDto; import org.sonar.db.organization.OrganizationDto; @@ -76,7 +77,7 @@ public class ShowActionTest { assertThat(show.since()).isEqualTo("4.4"); assertThat(show.isInternal()).isFalse(); assertThat(show.responseExampleAsString()).isNotEmpty(); - assertThat(show.params()).hasSize(3); + assertThat(show.params()).hasSize(4); } @Test @@ -160,6 +161,58 @@ public class ShowActionTest { } @Test + public void duplications_by_file_key_and_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + userSessionRule.addProjectPermission(UserRole.CODEVIEWER, project); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST)); + ComponentDto file = db.components().insertComponent(newFileDto(pullRequest)); + db.measures().insertLiveMeasure(file, dataMetric, m -> m.setData(format("<duplications>\n" + + " <g>\n" + + " <b s=\"31\" l=\"5\" r=\"%s\"/>\n" + + " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" + + " </g>\n" + + "</duplications>\n", file.getDbKey(), file.getDbKey()))); + + String result = ws.newRequest() + .setParam("key", file.getKey()) + .setParam("pullRequest", pullRequest.getPullRequest()) + .execute() + .getInput(); + + assertJson(result).isSimilarTo( + format("{\n" + + " \"duplications\": [\n" + + " {\n" + + " \"blocks\": [\n" + + " {\n" + + " \"from\": 20,\n" + + " \"size\": 5,\n" + + " \"_ref\": \"1\"\n" + + " },\n" + + " {\n" + + " \"from\": 31,\n" + + " \"size\": 5,\n" + + " \"_ref\": \"1\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"files\": {\n" + + " \"1\": {\n" + + " \"key\": \"%s\",\n" + + " \"name\": \"%s\",\n" + + " \"uuid\": \"%s\",\n" + + " \"project\": \"%s\",\n" + + " \"projectUuid\": \"%s\",\n" + + " \"projectName\": \"%s\"\n" + + " \"pullRequest\": \"%s\"\n" + + " }\n" + + " }\n" + + "}", + file.getKey(), file.longName(), file.uuid(), pullRequest.getKey(), pullRequest.uuid(), project.longName(), file.getPullRequest())); + } + + @Test public void fail_if_file_does_not_exist() { expectedException.expect(NotFoundException.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java index 67c85ae9b6a..4042c185b33 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/ShowResponseBuilderTest.java @@ -32,6 +32,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.test.JsonAssert; import static com.google.common.collect.Lists.newArrayList; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -53,7 +54,7 @@ public class ShowResponseBuilderTest { new DuplicationsParser.Duplication(file1, 57, 12), new DuplicationsParser.Duplication(file2, 73, 12)))); - test(blocks, null, + test(blocks, null, null, "{\n" + " \"duplications\": [\n" + " {\n" + @@ -98,7 +99,7 @@ public class ShowResponseBuilderTest { new DuplicationsParser.Duplication(file1, 57, 12), new DuplicationsParser.Duplication(file2, 73, 12)))); - test(blocks, null, + test(blocks, null, null, "{\n" + " \"duplications\": [\n" + " {\n" + @@ -139,7 +140,7 @@ public class ShowResponseBuilderTest { // Duplication on a removed file new DuplicationsParser.Duplication(null, 73, 12)))); - test(blocks, null, + test(blocks, null, null, "{\n" + " \"duplications\": [\n" + " {\n" + @@ -175,7 +176,7 @@ public class ShowResponseBuilderTest { new DuplicationsParser.Duplication(file1, 57, 12), new DuplicationsParser.Duplication(file2, 73, 12)))); - test(blocks, branch.getBranch(), + test(blocks, branch.getBranch(), null, "{\n" + " \"duplications\": [\n" + " {\n" + @@ -209,14 +210,58 @@ public class ShowResponseBuilderTest { } @Test + public void write_duplications_on_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto file1 = db.components().insertComponent(newFileDto(pullRequest)); + ComponentDto file2 = db.components().insertComponent(newFileDto(pullRequest)); + List<DuplicationsParser.Block> blocks = newArrayList(); + blocks.add(new DuplicationsParser.Block(newArrayList( + new DuplicationsParser.Duplication(file1, 57, 12), + new DuplicationsParser.Duplication(file2, 73, 12)))); + + test(blocks, null, pullRequest.getPullRequest(), + "{\n" + + " \"duplications\": [\n" + + " {\n" + + " \"blocks\": [\n" + + " {\n" + + " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" + + " },\n" + + " {\n" + + " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" + + " }\n" + + " ]\n" + + " }," + + " ],\n" + + " \"files\": {\n" + + " \"1\": {\n" + + " \"key\": \"" + file1.getKey() + "\",\n" + + " \"name\": \"" + file1.longName() + "\",\n" + + " \"project\": \"" + pullRequest.getKey() + "\",\n" + + " \"projectName\": \"" + pullRequest.longName() + "\",\n" + + " \"pullRequest\": \"" + pullRequest.getPullRequest() + "\",\n" + + " },\n" + + " \"2\": {\n" + + " \"key\": \"" + file2.getKey() + "\",\n" + + " \"name\": \"" + file2.longName() + "\",\n" + + " \"project\": \"" + pullRequest.getKey() + "\",\n" + + " \"projectName\": \"" + pullRequest.longName() + "\",\n" + + " \"pullRequest\": \"" + pullRequest.getPullRequest() + "\",\n" + + " }\n" + + " }" + + "}"); + } + + @Test public void write_nothing_when_no_data() { - test(Collections.emptyList(), null, "{\"duplications\": [], \"files\": {}}"); + test(Collections.emptyList(), null, null,"{\"duplications\": [], \"files\": {}}"); } - private void test(List<DuplicationsParser.Block> blocks, @Nullable String branch, String expected) { + private void test(List<DuplicationsParser.Block> blocks, @Nullable String branch, @Nullable String pullRequest, String expected) { StringWriter output = new StringWriter(); JsonWriter jsonWriter = JsonWriter.of(output); - ProtobufJsonFormat.write(underTest.build(db.getSession(), blocks, branch), jsonWriter); + ProtobufJsonFormat.write(underTest.build(db.getSession(), blocks, branch, pullRequest), jsonWriter); JsonAssert.assertJson(output.toString()).isSimilarTo(expected); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java index 26e244ece61..1ec5b97df72 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangeNotificationTest.java @@ -84,7 +84,7 @@ public class IssueChangeNotificationTest { @Test public void set_project_without_branch() { - IssueChangeNotification result = notification.setProject("MyService", "My Service", null); + IssueChangeNotification result = notification.setProject("MyService", "My Service", null, null); assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService"); assertThat(result.getFieldValue("projectName")).isEqualTo("My Service"); assertThat(result.getFieldValue("branch")).isNull(); @@ -92,13 +92,21 @@ public class IssueChangeNotificationTest { @Test public void set_project_with_branch() { - IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1"); + IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1", null); assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService"); assertThat(result.getFieldValue("projectName")).isEqualTo("My Service"); assertThat(result.getFieldValue("branch")).isEqualTo("feature1"); } @Test + public void set_project_with_pull_request() { + IssueChangeNotification result = notification.setProject("MyService", "My Service", null, "pr-123"); + assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService"); + assertThat(result.getFieldValue("projectName")).isEqualTo("My Service"); + assertThat(result.getFieldValue("pullRequest")).isEqualTo("pr-123"); + } + + @Test public void set_component() { IssueChangeNotification result = notification.setComponent(new ComponentDto().setDbKey("MyService").setLongName("My Service")); assertThat(result.getFieldValue("componentName")).isEqualTo("My Service"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java index b48a6aaad67..c608abfe57e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java @@ -170,7 +170,7 @@ public class IssueChangesEmailTemplateTest { Notification notification = new IssueChangeNotification() .setChangeAuthorLogin("simon") - .setProject("Struts", "org.apache:struts", null); + .setProject("Struts", "org.apache:struts", null, null); EmailMessage message = underTest.format(notification); assertThat(message.getFrom()).isEqualTo("Simon"); @@ -182,7 +182,7 @@ public class IssueChangesEmailTemplateTest { Notification notification = new IssueChangeNotification() .setChangeAuthorLogin("simon") - .setProject("Struts", "org.apache:struts", null); + .setProject("Struts", "org.apache:struts", null, null); EmailMessage message = underTest.format(notification); assertThat(message.getFrom()).isEqualTo("simon"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java index bfaff09a8b6..819bdc2f9bf 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java @@ -83,7 +83,7 @@ public class NewIssuesNotificationTest { @Test public void set_project_without_branch() { - underTest.setProject("project-key", "project-long-name", null); + underTest.setProject("project-key", "project-long-name", null, null); assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name"); assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key"); @@ -92,7 +92,7 @@ public class NewIssuesNotificationTest { @Test public void set_project_with_branch() { - underTest.setProject("project-key", "project-long-name", "feature"); + underTest.setProject("project-key", "project-long-name", "feature", null); assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name"); assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key"); @@ -100,6 +100,15 @@ public class NewIssuesNotificationTest { } @Test + public void set_project_with_pull_request() { + underTest.setProject("project-key", "project-long-name", null, "pr-123"); + + assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name"); + assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key"); + assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PULL_REQUEST)).isEqualTo("pr-123"); + } + + @Test public void set_project_version() { String version = randomAlphanumeric(5); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java index 3b7f3fdce8d..538917daf43 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java @@ -68,6 +68,8 @@ import static org.sonar.api.utils.DateUtils.addDays; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; +import static org.sonar.db.component.BranchType.PULL_REQUEST; +import static org.sonar.db.component.BranchType.SHORT; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newModuleDto; @@ -78,6 +80,7 @@ import static org.sonar.db.issue.IssueTesting.newIssue; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD; public class SearchActionComponentsTest { @@ -841,7 +844,7 @@ public class SearchActionComponentsTest { userSession.addProjectPermission(UserRole.USER, project); ComponentDto projectFile = db.components().insertComponent(newFileDto(project)); IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile)); - ComponentDto branch = db.components().insertProjectBranch(project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT)); ComponentDto branchFile = db.components().insertComponent(newFileDto(branch)); IssueDto branchIssue = db.issues().insertIssue(newIssue(rule, branch, branchFile)); allowAnyoneOnProjects(project); @@ -863,6 +866,34 @@ public class SearchActionComponentsTest { } @Test + public void search_by_pull_request() { + RuleDefinitionDto rule = db.rules().insert(); + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto projectFile = db.components().insertComponent(newFileDto(project)); + IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile)); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto pullRequestFile = db.components().insertComponent(newFileDto(pullRequest)); + IssueDto pullRequestIssue = db.issues().insertIssue(newIssue(rule, pullRequest, pullRequestFile)); + allowAnyoneOnProjects(project); + indexIssuesAndViews(); + + SearchWsResponse result = ws.newRequest() + .setParam(PARAM_COMPONENT_KEYS, pullRequest.getKey()) + .setParam(PARAM_PULL_REQUEST, pullRequest.getPullRequest()) + .executeProtobuf(SearchWsResponse.class); + + assertThat(result.getIssuesList()) + .extracting(Issue::getKey, Issue::getComponent, Issue::getPullRequest) + .containsExactlyInAnyOrder(tuple(pullRequestIssue.getKey(), pullRequestFile.getKey(), pullRequestFile.getPullRequest())); + assertThat(result.getComponentsList()) + .extracting(Issues.Component::getKey, Issues.Component::getPullRequest) + .containsExactlyInAnyOrder( + tuple(pullRequestFile.getKey(), pullRequestFile.getPullRequest()), + tuple(pullRequest.getKey(), pullRequest.getPullRequest())); + } + + @Test public void search_using_main_branch_name() { RuleDefinitionDto rule = db.rules().insert(); ComponentDto project = db.components().insertMainBranch(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index df3f8b2056a..b8283b57c8b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -135,7 +135,7 @@ public class SearchActionTest { assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( "additionalFields", "asc", "assigned", "assignees", "authors", "componentKeys", "componentRootUuids", "componentRoots", "componentUuids", "components", "branch", - "organization", + "pullRequest", "organization", "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "languages", "moduleUuids", "onComponentOnly", "p", "projectUuids", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java index 37bc2431355..4aff277f099 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java @@ -332,7 +332,7 @@ public class LiveMeasureComputerImplTest { markProjectAsAnalyzed(project); db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(Metric.Level.WARN.name())); db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null)); - BranchDto branch = db.getDbClient().branchDao().selectByKey(db.getSession(), project.projectUuid(), "master") + BranchDto branch = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), project.projectUuid(), "master") .orElseThrow(() -> new IllegalStateException("Can't find master branch")); List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java index 945165d987a..602d3418906 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java @@ -80,6 +80,18 @@ public class LiveQualityGateComputerImplTest { } @Test + public void loadQualityGate_returns_hardcoded_gate_for_pull_requests() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization); + BranchDto pullRequest = newBranchDto(project).setBranchType(BranchType.PULL_REQUEST); + db.components().insertProjectBranch(project, pullRequest); + + QualityGate result = underTest.loadQualityGate(db.getSession(), organization, project, pullRequest); + + assertThat(result).isSameAs(ShortLivingBranchQualityGate.GATE); + } + + @Test public void loadQualityGate_on_long_branch_returns_organization_default_gate() { OrganizationDto organization = db.organizations().insert(); ComponentDto project = db.components().insertPublicProject(organization); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java index 3b1542d0b7d..b900442a8ec 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java @@ -50,8 +50,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newProjectCopy; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.INT; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.server.component.ws.MeasuresWsParameters.DEPRECATED_PARAM_COMPONENT_ID; @@ -78,7 +80,7 @@ public class ComponentActionTest { assertThat(def.since()).isEqualTo("5.4"); assertThat(def.params()).extracting(Param::key) - .containsExactlyInAnyOrder("componentId", "component", "branch", "metricKeys", "additionalFields", "developerId", "developerKey"); + .containsExactlyInAnyOrder("componentId", "component", "branch", "pullRequest", "metricKeys", "additionalFields", "developerId", "developerKey"); assertThat(def.param("developerId").deprecatedSince()).isEqualTo("6.4"); assertThat(def.param("developerKey").deprecatedSince()).isEqualTo("6.4"); assertThat(def.param("componentId").deprecatedSince()).isEqualTo("6.6"); @@ -143,6 +145,29 @@ public class ComponentActionTest { } @Test + public void pull_request() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto complexity = db.measures().insertMetric(m1 -> m1.setKey("complexity").setValueType(INT.name())); + LiveMeasureDto measure = db.measures().insertLiveMeasure(file, complexity, m -> m.setValue(12.0d).setVariation(2.0d)); + + ComponentWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRIC_KEYS, complexity.getKey()) + .executeProtobuf(ComponentWsResponse.class); + + assertThat(response.getComponent()).extracting(Component::getKey, Component::getPullRequest) + .containsExactlyInAnyOrder(file.getKey(), "pr-123"); + assertThat(response.getComponent().getMeasuresList()) + .extracting(Measures.Measure::getMetric, m -> parseDouble(m.getValue())) + .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue())); + } + + @Test public void reference_uuid_in_the_response() { userSession.logIn().setRoot(); ComponentDto project = db.components().insertPrivateProject(); @@ -351,7 +376,7 @@ public class ComponentActionTest { db.components().insertProjectBranch(project, b -> b.setKey("my_branch")); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time"); + expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'"); ws.newRequest() .setParam(DEPRECATED_PARAM_COMPONENT_ID, file.uuid()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java index 36e71bc488d..cc42a73db20 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java @@ -68,6 +68,7 @@ import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; import static org.sonar.api.server.ws.WebService.Param.SORT; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newProjectCopy; @@ -82,6 +83,7 @@ import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KE import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_QUALIFIERS; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_STRATEGY; import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY; @@ -490,6 +492,29 @@ public class ComponentTreeActionTest { } @Test + public void pull_request() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto complexity = db.measures().insertMetric(m -> m.setValueType(INT.name())); + LiveMeasureDto measure = db.measures().insertLiveMeasure(file, complexity, m -> m.setValue(12.0d)); + + ComponentTreeWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRIC_KEYS, complexity.getKey()) + .executeProtobuf(Measures.ComponentTreeWsResponse.class); + + assertThat(response.getBaseComponent()).extracting(Measures.Component::getKey, Measures.Component::getPullRequest) + .containsExactlyInAnyOrder(file.getKey(), "pr-123"); + assertThat(response.getBaseComponent().getMeasuresList()) + .extracting(Measures.Measure::getMetric, m -> parseDouble(m.getValue())) + .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue())); + } + + @Test public void return_deprecated_id_in_the_response() { ComponentDto project = db.components().insertPrivateProject(); SnapshotDto analysis = db.components().insertSnapshot(project); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java index f56b7acecc1..0b366d51a50 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java @@ -62,12 +62,14 @@ import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED; import static org.sonar.db.component.SnapshotTesting.newAnalysis; import static org.sonar.db.measure.MeasureTesting.newMeasureDto; import static org.sonar.db.metric.MetricTesting.newMetricDto; +import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT; @@ -288,6 +290,29 @@ public class SearchHistoryActionTest { } @Test + public void pull_request() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + MeasureDto measure = db.measures().insertMeasure(file, analysis, nclocMetric, m -> m.setValue(2d)); + + SearchHistoryResponse result = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRICS, "ncloc") + .executeProtobuf(SearchHistoryResponse.class); + + assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(1); + HistoryMeasure historyMeasure = result.getMeasures(0); + assertThat(historyMeasure.getMetric()).isEqualTo(nclocMetric.getKey()); + assertThat(historyMeasure.getHistoryList()) + .extracting(m -> parseDouble(m.getValue())) + .containsExactlyInAnyOrder(measure.getValue()); + } + + @Test public void fail_when_using_branch_db_key() throws Exception { OrganizationDto organization = db.organizations().insert(); ComponentDto project = db.components().insertMainBranch(organization); @@ -372,7 +397,7 @@ public class SearchHistoryActionTest { assertThat(definition.isPost()).isFalse(); assertThat(definition.isInternal()).isFalse(); assertThat(definition.since()).isEqualTo("6.3"); - assertThat(definition.params()).hasSize(7); + assertThat(definition.params()).hasSize(8); Param branch = definition.param("branch"); assertThat(branch.since()).isEqualTo("6.6"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java index adf30f7b3b6..e5245cb7492 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java @@ -54,9 +54,11 @@ import static org.sonar.api.utils.DateUtils.formatDate; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.SnapshotTesting.newAnalysis; import static org.sonar.db.event.EventTesting.newEvent; +import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PULL_REQUEST; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.client.WsRequest.Method.POST; @@ -357,6 +359,24 @@ public class SearchActionTest { } @Test + public void pull_request() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(newAnalysis(branch)); + EventDto event = db.events().insertEvent(newEvent(analysis).setCategory(EventCategory.QUALITY_GATE.getLabel())); + + List<Analysis> result = call(SearchRequest.builder() + .setProject(project.getKey()) + .setPullRequest("pr-123") + .build()) + .getAnalysesList(); + + assertThat(result).extracting(Analysis::getKey).containsExactlyInAnyOrder(analysis.getUuid()); + assertThat(result.get(0).getEventsList()).extracting(Event::getKey).containsExactlyInAnyOrder(event.getUuid()); + } + + @Test public void empty_response() { ComponentDto project = db.components().insertPrivateProject(); userSession.addProjectPermission(UserRole.USER, project); @@ -422,7 +442,7 @@ public class SearchActionTest { assertThat(definition.responseExampleAsString()).isNotEmpty(); assertThat(definition.param("project").isRequired()).isTrue(); assertThat(definition.param("category")).isNotNull(); - assertThat(definition.params()).hasSize(7); + assertThat(definition.params()).hasSize(8); Param from = definition.param("from"); assertThat(from.since()).isEqualTo("6.5"); @@ -451,6 +471,7 @@ public class SearchActionTest { .setMethod(POST.name()); setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project)); setNullable(wsRequest.getBranch(), branch -> request.setParam(PARAM_BRANCH, branch)); + setNullable(wsRequest.getPullRequest(), branch -> request.setParam(PARAM_PULL_REQUEST, branch)); setNullable(wsRequest.getCategory(), category -> request.setParam(PARAM_CATEGORY, category.name())); setNullable(wsRequest.getPage(), page -> request.setParam(Param.PAGE, String.valueOf(page))); setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(Param.PAGE_SIZE, String.valueOf(pageSize))); diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java index 1effcf08f68..43a40176670 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java @@ -43,12 +43,15 @@ import org.sonar.api.utils.log.LoggerLevel; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.component.ComponentDto; import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue; +import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl.ChangedIssueImpl; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doThrow; @@ -224,20 +227,51 @@ public class QGChangeEventListenersImplTest { verifyNoMoreInteractions(listener1, listener2, listener3); } + @Test + public void test_status_mapping() { + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isEqualTo(QGChangeEventListener.Status.OPEN); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_REOPENED))).isEqualTo(QGChangeEventListener.Status.REOPENED); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CONFIRMED))).isEqualTo(QGChangeEventListener.Status.CONFIRMED); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_FP); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_WF); + assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FIXED))) + .isEqualTo(QGChangeEventListener.Status.RESOLVED_FIXED); + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CLOSED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("Unexpected status: CLOSED"); + } + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("A resolved issue should have a resolution"); + } + try { + ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_REMOVED)); + fail("Expected exception"); + } catch (Exception e) { + assertThat(e).hasMessage("Unexpected resolution for a resolved issue: REMOVED"); + } + } + private void verifyListenerCalled(QGChangeEventListener listener, QGChangeEvent changeEvent, DefaultIssue... issues) { ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor(); verify(listener).onIssueChanges(same(changeEvent), changedIssuesCaptor.capture()); Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue(); Tuple[] expected = Arrays.stream(issues) - .map(issue -> tuple(issue.key(), issue.status(), issue.type())) + .map(issue -> tuple(issue.key(), ChangedIssueImpl.statusOf(issue), issue.type())) .toArray(Tuple[]::new); assertThat(changedIssues) .hasSize(issues.length) - .extracting(ChangedIssue::getKey, t -> t.getStatus().name(), ChangedIssue::getType) + .extracting(ChangedIssue::getKey, t -> t.getStatus(), ChangedIssue::getType) .containsOnly(expected); } - private static final String[] STATUSES = Issue.STATUSES.stream().toArray(String[]::new); + private static final String[] POSSIBLE_STATUSES = asList(Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED).stream().toArray(String[]::new); private static int issueIdCounter = 0; private static DefaultIssue newDefaultIssue(String projectUuid) { @@ -245,10 +279,23 @@ public class QGChangeEventListenersImplTest { defaultIssue.setKey("issue_" + issueIdCounter++); defaultIssue.setProjectUuid(projectUuid); defaultIssue.setType(RuleType.values()[new Random().nextInt(RuleType.values().length)]); - defaultIssue.setStatus(STATUSES[new Random().nextInt(STATUSES.length)]); + defaultIssue.setStatus(POSSIBLE_STATUSES[new Random().nextInt(POSSIBLE_STATUSES.length)]); + String[] possibleResolutions = possibleResolutions(defaultIssue.getStatus()); + if (possibleResolutions.length > 0) { + defaultIssue.setResolution(possibleResolutions[new Random().nextInt(possibleResolutions.length)]); + } return defaultIssue; } + private static String[] possibleResolutions(String status) { + switch (status) { + case Issue.STATUS_RESOLVED: + return new String[] {Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX}; + default: + return new String[0]; + } + } + private static ComponentDto newComponentDto(String uuid) { ComponentDto componentDto = new ComponentDto(); componentDto.setUuid(uuid); diff --git a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java index b297d9612ff..49ab397b29c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java @@ -456,7 +456,7 @@ public class ListDefinitionsActionTest { assertThat(action.isInternal()).isFalse(); assertThat(action.isPost()).isFalse(); assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "branch"); + assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "branch", "pullRequest"); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ResetActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ResetActionTest.java index 6f7d06cf48e..320e37ea849 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ResetActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ResetActionTest.java @@ -238,7 +238,7 @@ public class ResetActionTest { assertThat(action.isInternal()).isFalse(); assertThat(action.isPost()).isTrue(); assertThat(action.responseExampleAsString()).isNull(); - assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("keys", "component", "branch"); + assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("keys", "component", "branch", "pullRequest"); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/SetActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/SetActionTest.java index 82b5ca6026c..8dee11d1b92 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/SetActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/SetActionTest.java @@ -1015,7 +1015,7 @@ public class SetActionTest { assertThat(definition.isInternal()).isFalse(); assertThat(definition.since()).isEqualTo("6.1"); assertThat(definition.params()).extracting(Param::key) - .containsOnly("key", "value", "values", "fieldValues", "component", "branch"); + .containsOnly("key", "value", "values", "fieldValues", "component", "branch", "pullRequest"); Param branch = definition.param("branch"); assertThat(branch.isInternal()).isTrue(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java index 5ee08886851..b2d1e54a598 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java @@ -871,7 +871,7 @@ public class ValuesActionTest { assertThat(action.isInternal()).isFalse(); assertThat(action.isPost()).isFalse(); assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("keys", "component", "branch"); + assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("keys", "component", "branch", "pullRequest"); } private ValuesWsResponse executeRequestForComponentProperties(ComponentDto componentDto, String... keys) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java index 30e5de83212..25cfea083de 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/LinesActionTest.java @@ -47,6 +47,7 @@ import static java.lang.String.format; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; public class LinesActionTest { @@ -64,14 +65,14 @@ public class LinesActionTest { @Rule public UserSessionRule userSession = UserSessionRule.standalone(); - SourceService sourceService; - HtmlSourceDecorator htmlSourceDecorator; - ComponentDao componentDao; + private SourceService sourceService; + private HtmlSourceDecorator htmlSourceDecorator; + private ComponentDao componentDao; - ComponentDto project; - ComponentDto file; + private ComponentDto project; + private ComponentDto file; - WsTester wsTester; + private WsTester wsTester; @Before public void setUp() { @@ -142,6 +143,26 @@ public class LinesActionTest { } @Test + public void pull_request() throws Exception { + ComponentDto project = db.components().insertMainBranch(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + db.getDbClient().fileSourceDao().insert(db.getSession(), new FileSourceDto() + .setProjectUuid(branch.uuid()) + .setFileUuid(file.uuid()) + .setSourceData(FileSourceTesting.newFakeData(3).build())); + db.commit(); + userSession.logIn("login").addProjectPermission(UserRole.CODEVIEWER, project, file); + + WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines") + .setParam("key", file.getKey()) + .setParam("pullRequest", file.getPullRequest()); + + request.execute().assertJson(getClass(), "show_source.json"); + } + + @Test public void fail_when_no_uuid_or_key_param() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Either 'uuid' or 'key' must be provided"); @@ -285,7 +306,7 @@ public class LinesActionTest { db.components().insertProjectBranch(project, b -> b.setKey("my_branch")); expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'uuid' and 'branch' parameters cannot be used at the same time"); + expectedException.expectMessage("Parameter 'uuid' cannot be used at the same time as 'branch' or 'pullRequest'"); wsTester.newGetRequest("api/sources", "lines") .setParam("uuid", file.uuid()) diff --git a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java index 9518ba177e2..bfe7f515dc3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java @@ -64,7 +64,7 @@ public class SourcesWsTest { assertThat(raw.since()).isEqualTo("5.0"); assertThat(raw.isInternal()).isFalse(); assertThat(raw.responseExampleAsString()).isNotEmpty(); - assertThat(raw.params()).hasSize(2); + assertThat(raw.params()).hasSize(3); WebService.Action lines = controller.action("lines"); assertThat(lines).isNotNull(); @@ -72,7 +72,7 @@ public class SourcesWsTest { assertThat(lines.since()).isEqualTo("5.0"); assertThat(lines.isInternal()).isTrue(); assertThat(lines.responseExampleAsString()).isNotEmpty(); - assertThat(lines.params()).hasSize(5); + assertThat(lines.params()).hasSize(6); WebService.Action hash = controller.action("hash"); assertThat(hash).isNotNull(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/test/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/test/ws/ListActionTest.java index 59491622a70..d99241c41a9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/test/ws/ListActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/test/ws/ListActionTest.java @@ -52,9 +52,11 @@ import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; import static org.sonar.api.web.UserRole.CODEVIEWER; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.protobuf.DbFileSources.Test.TestStatus.OK; import static org.sonar.server.test.db.TestTesting.newTest; +import static org.sonar.server.test.ws.ListAction.PARAM_PULL_REQUEST; import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_ID; import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_KEY; import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_LINE_NUMBER; @@ -99,7 +101,7 @@ public class ListActionTest { assertThat(action.isPost()).isFalse(); assertThat(action.handler()).isNotNull(); assertThat(action.responseExampleAsString()).isNotEmpty(); - assertThat(action.params()).hasSize(9); + assertThat(action.params()).hasSize(10); assertThat(action.description()).isEqualTo("Get the list of tests either in a test file or that test a given line of source code.<br /> " + "Requires 'Browse' permission on the file's project.<br /> " + "One (and only one) of the following combination of parameters must be provided: " + @@ -209,6 +211,29 @@ public class ListActionTest { } @Test + public void list_tests_by_test_file_key_and_pull_request() { + ComponentDto project = db.components().insertMainBranch(); + userSessionRule.addProjectPermission(CODEVIEWER, project); + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); + ComponentDto mainFile = db.components().insertComponent(newFileDto(pullRequest)); + ComponentDto testFile = db.components().insertComponent(newFileDto(pullRequest).setQualifier(UNIT_TEST_FILE)); + + DbFileSources.Test test1 = newTest(mainFile, 10).build(); + DbFileSources.Test test2 = newTest(mainFile, 11).build(); + insertTests(testFile, test1, test2); + + ListResponse request = call(ws.newRequest() + .setParam(TEST_FILE_KEY, testFile.getKey()) + .setParam(PARAM_PULL_REQUEST, testFile.getPullRequest())); + + assertThat(request.getTestsList()) + .extracting(Tests.Test::getId, Tests.Test::getFileKey, Tests.Test::getFilePullRequest) + .containsOnly( + tuple(test1.getUuid(), testFile.getKey(), testFile.getPullRequest()), + tuple(test2.getUuid(), testFile.getKey(), testFile.getPullRequest())); + } + + @Test public void list_tests_by_source_file_uuid_and_line_number() { userSessionRule.addProjectPermission(CODEVIEWER, project); ComponentDto anotherMainFile = db.components().insertComponent(newFileDto(project)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java index 7415178a93d..be8df1f933e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java @@ -241,8 +241,8 @@ public class WebhookPayloadFactoryImplTest { assertJson(payload.getJson()) .isSimilarTo("{" + "\"branch\": {" + - " \"name\": \"feature/foo\"" + - " \"type\": \"SHORT\"" + + " \"name\": \"feature/foo\"," + + " \"type\": \"SHORT\"," + " \"isMain\": false," + " \"url\": \"http://foo/project/issues?branch=feature%2Ffoo&id=P1&resolved=false\"" + "}" + @@ -250,6 +250,23 @@ public class WebhookPayloadFactoryImplTest { } @Test + public void create_payload_on_pull_request() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "pr/foo", Branch.Type.PULL_REQUEST), 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + "\"branch\": {" + + " \"name\": \"pr/foo\"," + + " \"type\": \"PULL_REQUEST\"," + + " \"isMain\": false," + + " \"url\": \"http://foo/project/issues?pullRequest=pr%2Ffoo&id=P1&resolved=false\"" + + "}" + + "}"); + } + + @Test public void create_without_ce_task() { ProjectAnalysis analysis = newAnalysis(null, null, null, null, emptyMap()); diff --git a/sonar-core/src/main/java/org/sonar/core/config/ScannerProperties.java b/sonar-core/src/main/java/org/sonar/core/config/ScannerProperties.java index 1cc3bddeef5..a9c3088f6fb 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/ScannerProperties.java +++ b/sonar-core/src/main/java/org/sonar/core/config/ScannerProperties.java @@ -31,9 +31,14 @@ public class ScannerProperties { public static final String BRANCHES_DOC_LINK = "https://redirect.sonarsource.com/doc/branches.html"; + public static final String ORGANIZATION = "sonar.organization"; + public static final String BRANCH_NAME = "sonar.branch.name"; public static final String BRANCH_TARGET = "sonar.branch.target"; - public static final String ORGANIZATION = "sonar.organization"; + + public static final String PULL_REQUEST_KEY = "sonar.pullrequest.key"; + public static final String PULL_REQUEST_BRANCH = "sonar.pullrequest.branch"; + public static final String PULL_REQUEST_BASE = "sonar.pullrequest.base"; public static final String LINKS_SOURCES_DEV = "sonar.links.scm_dev"; @@ -70,8 +75,20 @@ public class ScannerProperties { PropertyDefinition.builder(BRANCH_TARGET) .name("Optional name of target branch to merge into") .description( - "Defines what is the target branch of the branch being analyzed. The main branch cannot have a target. " - + "If no target is defined for other branches, the main branch is used as a target.") + "Defines the target branch of the branch being analyzed. The main branch cannot have a target. " + + "If no target is defined, the main branch is used as the target.") + .hidden() + .build(), + PropertyDefinition.builder(PULL_REQUEST_BRANCH) + .name("Optional name of pull request") + .description("Provide a name for the pull request being analyzed. It might match an existing pull request of the project, otherwise a new pull request will be created.") + .hidden() + .build(), + PropertyDefinition.builder(PULL_REQUEST_BASE) + .name("Optional name of target branch to merge into") + .description( + "Defines the target branch of the pull request being analyzed. " + + "If no target is defined, the main branch is used as the target.") .hidden() .build()); } diff --git a/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java b/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java index a3cfb1e2bc5..b3054a31a22 100644 --- a/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java @@ -30,7 +30,7 @@ public class CorePropertyDefinitionsTest { @Test public void all() { List<PropertyDefinition> defs = CorePropertyDefinitions.all(); - assertThat(defs).hasSize(57); + assertThat(defs).hasSize(59); } @Test diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java b/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java index c64ccaf898d..50c5f39e2fe 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java @@ -27,7 +27,7 @@ import java.util.Optional; public interface Branch { enum Type { - LONG, SHORT + LONG, SHORT, PULL_REQUEST } boolean isMain(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdExecutor.java index 5d47999757e..b143361ec41 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdExecutor.java @@ -83,8 +83,8 @@ public class CpdExecutor { } public void execute() { - if (branchConfiguration.isShortLivingBranch()) { - LOG.info("Skipping CPD calculation for short living branch"); + if (branchConfiguration.isShortOrPullRequest()) { + LOG.info("Skipping CPD calculation for short living branch and pull request"); return; } execute(TIMEOUT); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java index c1edd6f92f7..c36b8a196e6 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java @@ -152,7 +152,8 @@ public class ComponentsPublisher implements ReportPublisherStep { } private boolean shouldSkipComponent(DefaultInputComponent component, Collection<InputComponent> children) { - if (component instanceof InputModule && children.isEmpty() && branchConfiguration.isShortLivingBranch()) { + if (component instanceof InputModule && children.isEmpty() + && (branchConfiguration.isShortOrPullRequest())) { // no children on a module in short branch analysis -> skip it (except root) return !moduleHierarchy.isRoot((InputModule) component); } else if (component instanceof InputDir && children.isEmpty()) { @@ -165,7 +166,7 @@ public class ComponentsPublisher implements ReportPublisherStep { } else if (component instanceof DefaultInputFile) { // skip files not marked for publishing DefaultInputFile inputFile = (DefaultInputFile) component; - return !inputFile.isPublished() || (branchConfiguration.isShortLivingBranch() && inputFile.status() == Status.SAME); + return !inputFile.isPublished() || (branchConfiguration.isShortOrPullRequest() && inputFile.status() == Status.SAME); } return false; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java index 202d8edae8f..a17c691f8bb 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java @@ -91,30 +91,13 @@ public class MetadataPublisher implements ReportPublisherStep { settings.get(ORGANIZATION).ifPresent(builder::setOrganizationKey); if (branchConfiguration.branchName() != null) { - builder.setBranchName(branchConfiguration.branchName()); - builder.setBranchType(toProtobufBranchType(branchConfiguration.branchType())); - String branchTarget = branchConfiguration.branchTarget(); - if (branchTarget != null) { - builder.setMergeBranchName(branchTarget); - } + addBranchInformation(builder); } + Optional.ofNullable(rootProject.getBranch()).ifPresent(builder::setDeprecatedBranch); if (scmConfiguration != null) { - ScmProvider scmProvider = scmConfiguration.provider(); - if (scmProvider != null) { - Path projectBasedir = moduleHierarchy.root().getBaseDir(); - try { - builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir))); - } catch (UnsupportedOperationException e) { - LOG.debug(e.getMessage()); - } - try { - builder.setScmRevisionId(scmProvider.revisionId(projectBasedir)); - } catch (UnsupportedOperationException e) { - LOG.debug(e.getMessage()); - } - } + addScmInformation(builder); } for (QProfile qp : qProfiles.findAll()) { @@ -132,7 +115,40 @@ public class MetadataPublisher implements ReportPublisherStep { writer.writeMetadata(builder.build()); } + private void addScmInformation(ScannerReport.Metadata.Builder builder) { + ScmProvider scmProvider = scmConfiguration.provider(); + if (scmProvider != null) { + Path projectBasedir = moduleHierarchy.root().getBaseDir(); + try { + builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir))); + } catch (UnsupportedOperationException e) { + LOG.debug(e.getMessage()); + } + try { + builder.setScmRevisionId(scmProvider.revisionId(projectBasedir)); + } catch (UnsupportedOperationException e) { + LOG.debug(e.getMessage()); + } + } + } + + private void addBranchInformation(ScannerReport.Metadata.Builder builder) { + builder.setBranchName(branchConfiguration.branchName()); + BranchType branchType = toProtobufBranchType(branchConfiguration.branchType()); + builder.setBranchType(branchType); + String branchTarget = branchConfiguration.branchTarget(); + if (branchTarget != null) { + builder.setMergeBranchName(branchTarget); + } + if (branchType == BranchType.PULL_REQUEST) { + builder.setPullRequestKey(branchConfiguration.pullRequestKey()); + } + } + private static BranchType toProtobufBranchType(org.sonar.scanner.scan.branch.BranchType branchType) { + if (branchType == org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST) { + return BranchType.PULL_REQUEST; + } if (branchType == org.sonar.scanner.scan.branch.BranchType.LONG) { return BranchType.LONG; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java index 58066ab0351..97b01015e2c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java @@ -57,6 +57,7 @@ import org.sonarqube.ws.client.WsResponse; import static org.sonar.core.config.ScannerProperties.BRANCH_NAME; import static org.sonar.core.config.ScannerProperties.ORGANIZATION; import static org.sonar.core.util.FileUtils.deleteQuietly; +import static org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST; @ScannerSide public class ReportPublisher implements Startable { @@ -180,8 +181,12 @@ public class ReportPublisher implements Startable { String branchName = branchConfiguration.branchName(); if (branchName != null) { - post.setParam(CHARACTERISTIC, "branch=" + branchName); - post.setParam(CHARACTERISTIC, "branchType=" + branchConfiguration.branchType().name()); + if (branchConfiguration.branchType() != PULL_REQUEST) { + post.setParam(CHARACTERISTIC, "branch=" + branchName); + post.setParam(CHARACTERISTIC, "branchType=" + branchConfiguration.branchType().name()); + } else { + post.setParam(CHARACTERISTIC, "pullRequest=" + branchConfiguration.pullRequestKey()); + } } WsResponse response; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java index f63b163ae37..45b9e7f139e 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisher.java @@ -55,7 +55,7 @@ public class TestExecutionAndCoveragePublisher implements ReportPublisherStep { @Override public void publish(ScannerReportWriter writer) { - if (branchConfiguration.isShortLivingBranch()) { + if (branchConfiguration.isShortOrPullRequest()) { return; } final ScannerReport.Test.Builder testBuilder = ScannerReport.Test.newBuilder(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java index 15a5b42076c..94233256871 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java @@ -23,17 +23,26 @@ import com.google.common.base.Joiner; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; import org.sonar.api.batch.AnalysisMode; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.utils.MessageException; import org.sonar.core.component.ComponentKeys; -import org.sonar.core.config.ScannerProperties; import org.sonar.scanner.bootstrap.GlobalConfiguration; import org.sonar.scanner.scan.branch.BranchParamsValidator; +import static java.lang.String.format; +import static java.util.Objects.nonNull; +import static org.apache.commons.lang.StringUtils.isNotEmpty; +import static org.sonar.core.config.ScannerProperties.BRANCHES_DOC_LINK; +import static org.sonar.core.config.ScannerProperties.BRANCH_NAME; +import static org.sonar.core.config.ScannerProperties.BRANCH_TARGET; +import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BASE; +import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BRANCH; +import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_KEY; + /** * This class aims at validating project reactor * @since 3.6 @@ -69,11 +78,11 @@ public class ProjectReactorValidator { String deprecatedBranchName = reactor.getRoot().getBranch(); - if (branchParamsValidator != null) { - // branch plugin is present + if (isBranchFeatureAvailable()) { branchParamsValidator.validate(validationMessages, deprecatedBranchName); } else { validateBranchParamsWhenPluginAbsent(validationMessages); + validatePullRequestParamsWhenPluginAbsent(validationMessages); } validateBranch(validationMessages, deprecatedBranchName); @@ -84,38 +93,49 @@ public class ProjectReactorValidator { } private void validateBranchParamsWhenPluginAbsent(List<String> validationMessages) { - for (String param : Arrays.asList(ScannerProperties.BRANCH_NAME, ScannerProperties.BRANCH_TARGET)) { - if (StringUtils.isNotEmpty(settings.get(param).orElse(null))) { - validationMessages.add(String.format("To use the property \"%s\", the branch plugin is required but not installed. " - + "See the documentation of branch support: %s.", param, ScannerProperties.BRANCHES_DOC_LINK)); + for (String param : Arrays.asList(BRANCH_NAME, BRANCH_TARGET)) { + if (isNotEmpty(settings.get(param).orElse(null))) { + validationMessages.add(format("To use the property \"%s\", the branch plugin is required but not installed. " + + "See the documentation of branch support: %s.", param, BRANCHES_DOC_LINK)); } } } + private void validatePullRequestParamsWhenPluginAbsent(List<String> validationMessages) { + Stream.of(PULL_REQUEST_KEY, PULL_REQUEST_BRANCH, PULL_REQUEST_BASE) + .filter(param -> nonNull(settings.get(param).orElse(null))) + .forEach(param -> validationMessages.add(format("To use the property \"%s\", the branch plugin is required but not installed. " + + "See the documentation of branch support: %s.", param, BRANCHES_DOC_LINK))); + } + private static void validateModuleIssuesMode(ProjectDefinition moduleDef, List<String> validationMessages) { if (!ComponentKeys.isValidModuleKeyIssuesMode(moduleDef.getKey())) { - validationMessages.add(String.format("\"%s\" is not a valid project or module key. " + validationMessages.add(format("\"%s\" is not a valid project or module key. " + "Allowed characters in issues mode are alphanumeric, '-', '_', '.', '/' and ':', with at least one non-digit.", moduleDef.getKey())); } } private static void validateModule(ProjectDefinition moduleDef, List<String> validationMessages) { if (!ComponentKeys.isValidModuleKey(moduleDef.getKey())) { - validationMessages.add(String.format("\"%s\" is not a valid project or module key. " + validationMessages.add(format("\"%s\" is not a valid project or module key. " + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", moduleDef.getKey())); } String originalVersion = moduleDef.getOriginalVersion(); if (originalVersion != null && originalVersion.length() > 100) { - validationMessages.add(String.format("\"%s\" is not a valid version name for module \"%s\". " + + validationMessages.add(format("\"%s\" is not a valid version name for module \"%s\". " + "The maximum length for version numbers is 100 characters.", originalVersion, moduleDef.getKey())); } } private static void validateBranch(List<String> validationMessages, @Nullable String branch) { - if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) { - validationMessages.add(String.format("\"%s\" is not a valid branch name. " + if (isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) { + validationMessages.add(format("\"%s\" is not a valid branch name. " + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch)); } } + private boolean isBranchFeatureAvailable() { + return branchParamsValidator != null; + } + } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index b57cc8f10b2..3dd06bbab78 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -20,6 +20,7 @@ package org.sonar.scanner.scan; import com.google.common.annotations.VisibleForTesting; +import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; import org.sonar.api.batch.InstantiationStrategy; @@ -91,6 +92,7 @@ import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchConfigurationProvider; import org.sonar.scanner.scan.branch.BranchType; import org.sonar.scanner.scan.branch.ProjectBranchesProvider; +import org.sonar.scanner.scan.branch.ProjectPullRequestsProvider; import org.sonar.scanner.scan.filesystem.BatchIdGenerator; import org.sonar.scanner.scan.filesystem.InputComponentStoreProvider; import org.sonar.scanner.scan.filesystem.StatusDetection; @@ -146,6 +148,7 @@ public class ProjectScanContainer extends ComponentContainer { new RulesProvider(), new BranchConfigurationProvider(), new ProjectBranchesProvider(), + new ProjectPullRequestsProvider(), DefaultAnalysisMode.class, new ProjectRepositoriesProvider(), @@ -254,7 +257,13 @@ public class ProjectScanContainer extends ComponentContainer { String branchName = props.property(ScannerProperties.BRANCH_NAME); if (branchName != null) { BranchConfiguration branchConfig = getComponentByType(BranchConfiguration.class); - LOG.info("Branch name: {}, type: {}", branchName, toDisplayName(branchConfig.branchType())); + LOG.info("Branch name: {}, type: {}", branchName, branchTypeToDisplayName(branchConfig.branchType())); + } + + String pullRequestBranch = props.property(ScannerProperties.PULL_REQUEST_BRANCH); + if (pullRequestBranch != null) { + String pullRequestBase = props.property(ScannerProperties.PULL_REQUEST_BASE); + LOG.info("Pull request into {}: {}", pullRequestBaseToDisplayName(pullRequestBase), pullRequestBranch); } LOG.debug("Start recursive analysis of project modules"); @@ -265,7 +274,11 @@ public class ProjectScanContainer extends ComponentContainer { } } - private static String toDisplayName(BranchType branchType) { + private static String pullRequestBaseToDisplayName(@Nullable String pullRequestBase) { + return pullRequestBase != null ? pullRequestBase : "default branch"; + } + + private static String branchTypeToDisplayName(BranchType branchType) { switch (branchType) { case LONG: return "long living"; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfiguration.java index 4ddcff98abd..695c3c6d62d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfiguration.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfiguration.java @@ -36,8 +36,8 @@ public interface BranchConfiguration { */ BranchType branchType(); - default boolean isShortLivingBranch() { - return branchType() == BranchType.SHORT; + default boolean isShortOrPullRequest() { + return branchType() == BranchType.PULL_REQUEST || branchType() == BranchType.SHORT; } /** @@ -54,7 +54,19 @@ public interface BranchConfiguration { /** * The name of the base branch to determine project repository and changed files. + * + * Note: this is important for the scanner during the analysis of long living branches. + * For short living branches, branchBase is always the same as branchTarget. + * For long living branches, branchBase is the target in case of first analysis, + * otherwise it's the branch itself. */ @CheckForNull String branchBase(); + + /** + * The key of the pull request. + * + * @throws IllegalStateException if this branch configuration is not a pull request. + */ + String pullRequestKey(); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationLoader.java index 24b57650ee9..5b845195d05 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationLoader.java @@ -27,5 +27,5 @@ import org.sonar.api.batch.ScannerSide; @ScannerSide @InstantiationStrategy(InstantiationStrategy.PER_BATCH) public interface BranchConfigurationLoader { - BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> remoteSettingsSupplier, ProjectBranches branches); + BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> remoteSettingsSupplier, ProjectBranches branches, ProjectPullRequests pullRequests); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationProvider.java index 08caee68ca2..276ba411c28 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchConfigurationProvider.java @@ -39,14 +39,14 @@ public class BranchConfigurationProvider extends ProviderAdapter { private BranchConfiguration branchConfiguration = null; public BranchConfiguration provide(@Nullable BranchConfigurationLoader loader, GlobalConfiguration globalConfiguration, ProjectKey projectKey, - SettingsLoader settingsLoader, ProjectBranches branches) { + SettingsLoader settingsLoader, ProjectBranches branches, ProjectPullRequests pullRequests) { if (branchConfiguration == null) { if (loader == null) { branchConfiguration = new DefaultBranchConfiguration(); } else { Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); Supplier<Map<String, String>> settingsSupplier = createSettingsSupplier(globalConfiguration, projectKey, settingsLoader); - branchConfiguration = loader.load(globalConfiguration.getProperties(), settingsSupplier, branches); + branchConfiguration = loader.load(globalConfiguration.getProperties(), settingsSupplier, branches, pullRequests); profiler.stopInfo(); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchType.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchType.java index b90f633ded0..e8143ce4480 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchType.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/BranchType.java @@ -20,5 +20,5 @@ package org.sonar.scanner.scan.branch; public enum BranchType { - SHORT, LONG + SHORT, LONG, PULL_REQUEST } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchConfiguration.java index 045c3e3ccd4..2e048e6c689 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchConfiguration.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/DefaultBranchConfiguration.java @@ -46,4 +46,9 @@ public class DefaultBranchConfiguration implements BranchConfiguration { public String branchBase() { return null; } + + @Override + public String pullRequestKey() { + throw new IllegalStateException("Only a branch of type PULL_REQUEST can have a pull request id."); + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java index 2a59148dbd0..043360fb64f 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectBranchesProvider.java @@ -26,8 +26,6 @@ import org.sonar.api.batch.bootstrap.ProjectKey; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; -import org.sonar.core.config.ScannerProperties; -import org.sonar.scanner.bootstrap.GlobalConfiguration; public class ProjectBranchesProvider extends ProviderAdapter { @@ -36,16 +34,19 @@ public class ProjectBranchesProvider extends ProviderAdapter { private ProjectBranches branches = null; - public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProjectKey projectKey, GlobalConfiguration settings) { - if (branches == null) { - if (loader == null || !settings.get(ScannerProperties.BRANCH_NAME).isPresent()) { - branches = new ProjectBranches(Collections.emptyList()); - } else { - Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); - branches = loader.load(projectKey.get()); - profiler.stopInfo(); - } + public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProjectKey projectKey) { + if (branches != null) { + return branches; } + + if (loader == null) { + branches = new ProjectBranches(Collections.emptyList()); + return branches; + } + + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + branches = loader.load(projectKey.get()); + profiler.stopInfo(); return branches; } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequests.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequests.java new file mode 100644 index 00000000000..a9f4c2c3d07 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequests.java @@ -0,0 +1,49 @@ +/* + * 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.scanner.scan.branch; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; + +/** + * Container class for information about the pull requests of a project. + */ +@Immutable +public class ProjectPullRequests { + + private final Map<String, PullRequestInfo> pullRequestsById; + + public ProjectPullRequests(List<PullRequestInfo> pullRequestsById) { + this.pullRequestsById = pullRequestsById.stream().collect(Collectors.toMap(PullRequestInfo::getBranch, Function.identity())); + } + + @CheckForNull + public PullRequestInfo get(String branch) { + return pullRequestsById.get(branch); + } + + public boolean isEmpty() { + return pullRequestsById.isEmpty(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsLoader.java new file mode 100644 index 00000000000..525afd56f38 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsLoader.java @@ -0,0 +1,34 @@ +/* + * 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.scanner.scan.branch; + +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.ScannerSide; + +@ScannerSide +@InstantiationStrategy(InstantiationStrategy.PER_BATCH) +public interface ProjectPullRequestsLoader { + + /** + * Load the pull requests of a project. + */ + ProjectPullRequests load(String projectKey); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProvider.java new file mode 100644 index 00000000000..15023c8c2fc --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProvider.java @@ -0,0 +1,51 @@ +/* + * 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.scanner.scan.branch; + +import java.util.Collections; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.bootstrap.ProjectKey; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +public class ProjectPullRequestsProvider extends ProviderAdapter { + + private static final Logger LOG = Loggers.get(ProjectPullRequestsProvider.class); + private static final String LOG_MSG = "Load project pull requests"; + + private ProjectPullRequests pullRequests = null; + + public ProjectPullRequests provide(@org.picocontainer.annotations.Nullable ProjectPullRequestsLoader loader, ProjectKey projectKey) { + if (pullRequests != null) { + return pullRequests; + } + + if (loader == null) { + pullRequests = new ProjectPullRequests(Collections.emptyList()); + return pullRequests; + } + + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + pullRequests = loader.load(projectKey.get()); + profiler.stopInfo(); + return pullRequests; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/PullRequestInfo.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/PullRequestInfo.java new file mode 100644 index 00000000000..6ccc3656fb5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/branch/PullRequestInfo.java @@ -0,0 +1,53 @@ +/* + * 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.scanner.scan.branch; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Container class for information about a pull request. + */ +@Immutable +public class PullRequestInfo { + private final String id; + private final String branch; + private final String base; + + public PullRequestInfo(String id, String branch, @Nullable String base) { + this.id = id; + this.branch = branch; + this.base = base; + } + + public String getId() { + return id; + } + + public String getBranch() { + return branch; + } + + @CheckForNull + public String getBase() { + return base; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStore.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStore.java index 3ea00232847..e6d96e51733 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStore.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStore.java @@ -80,7 +80,8 @@ public class InputComponentStore { return inputFileCache.values().stream() .map(f -> (DefaultInputFile) f) .filter(DefaultInputFile::isPublished) - .filter(f -> (!branchConfiguration.isShortLivingBranch()) || f.status() != Status.SAME)::iterator; + .filter(f -> !branchConfiguration.isShortOrPullRequest() || f.status() != Status.SAME) + ::iterator; } public Iterable<InputFile> allFiles() { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java index 38e2416bfad..a04b4f5021a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmChangedFilesProvider.java @@ -62,7 +62,7 @@ public class ScmChangedFilesProvider extends ProviderAdapter { @CheckForNull private static Collection<Path> loadChangedFilesIfNeeded(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, Path rootBaseDir) { - if (branchConfiguration.isShortLivingBranch() && branchConfiguration.branchTarget() != null) { + if (branchConfiguration.isShortOrPullRequest() && branchConfiguration.branchTarget() != null) { ScmProvider scmProvider = scmConfiguration.provider(); if (scmProvider != null) { Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java index cbdafc68bcd..bb587269fd9 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java @@ -103,7 +103,7 @@ public final class ScmPublisher { } if (configuration.forceReloadAll() || f.status() != Status.SAME) { addIfNotEmpty(filesToBlame, f); - } else if (!branchConfiguration.isShortLivingBranch()) { + } else if (!branchConfiguration.isShortOrPullRequest()) { // File status is SAME so that mean fileData exists FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile.getModuleRelativePath()); if (StringUtils.isEmpty(fileData.revision())) { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java index 6947f918d60..6972e241500 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java @@ -148,7 +148,7 @@ public class DefaultSensorContext implements SensorContext { @Override public NewCoverage newCoverage() { - if (branchConfiguration.isShortLivingBranch()) { + if (branchConfiguration.isShortOrPullRequest()) { return NO_OP_NEW_COVERAGE; } return new DefaultCoverage(sensorStorage); @@ -156,7 +156,7 @@ public class DefaultSensorContext implements SensorContext { @Override public NewCpdTokens newCpdTokens() { - if (analysisMode.isIssues() || branchConfiguration.isShortLivingBranch()) { + if (analysisMode.isIssues() || branchConfiguration.isShortOrPullRequest()) { return NO_OP_NEW_CPD_TOKENS; } return new DefaultCpdTokens(config, sensorStorage); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java index 43d9198a06c..6b5a7137d94 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java @@ -355,7 +355,7 @@ public class DefaultSensorStorage implements SensorStorage { } private boolean shouldSkipStorage(DefaultInputFile defaultInputFile) { - return branchConfiguration.isShortLivingBranch() && defaultInputFile.status() == InputFile.Status.SAME; + return branchConfiguration.isShortOrPullRequest() && defaultInputFile.status() == InputFile.Status.SAME; } /** diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdExecutorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdExecutorTest.java index 8605ab5418d..f685e4f22cf 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdExecutorTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdExecutorTest.java @@ -100,7 +100,18 @@ public class CpdExecutorTest { @Test public void skipIfShortBranch() { - when(branchConfig.isShortLivingBranch()).thenReturn(true); + when(branchConfig.isShortOrPullRequest()).thenReturn(true); + index = mock(SonarCpdBlockIndex.class); + executor = new CpdExecutor(settings, index, publisher, componentStore, branchConfig); + + executor.execute(); + + verifyZeroInteractions(index); + } + + @Test + public void skip_if_pull_request() { + when(branchConfig.isShortOrPullRequest()).thenReturn(true); index = mock(SonarCpdBlockIndex.class); executor = new CpdExecutor(settings, index, publisher, componentStore, branchConfig); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java index 71d86e98de6..eb28ba1a3d0 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java @@ -72,6 +72,7 @@ import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchConfigurationLoader; import org.sonar.scanner.scan.branch.BranchType; import org.sonar.scanner.scan.branch.ProjectBranches; +import org.sonar.scanner.scan.branch.ProjectPullRequests; import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; import org.sonarqube.ws.Rules.ListResponse.Rule; @@ -420,6 +421,11 @@ public class ScannerMediumTester extends ExternalResource { public String branchBase() { return branchBase; } + + @Override + public String pullRequestKey() { + throw new UnsupportedOperationException(); + } } public ScannerMediumTester setBranchType(BranchType branchType) { @@ -439,7 +445,7 @@ public class ScannerMediumTester extends ExternalResource { private class FakeBranchConfigurationLoader implements BranchConfigurationLoader { @Override - public BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> settingsSupplier, ProjectBranches branches) { + public BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> settingsSupplier, ProjectBranches branches, ProjectPullRequests pullRequests) { return branchConfiguration; } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java index eda36629c96..1e84cff4434 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ComponentsPublisherTest.java @@ -310,7 +310,77 @@ public class ComponentsPublisherTest { @Test public void skip_unchanged_components_in_short_branches() throws IOException { - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); + ProjectAnalysisInfo projectAnalysisInfo = mock(ProjectAnalysisInfo.class); + when(projectAnalysisInfo.analysisDate()).thenReturn(DateUtils.parseDate("2012-12-12")); + + Path moduleBaseDir = temp.newFolder().toPath(); + ProjectDefinition rootDef = ProjectDefinition.create() + .setKey("foo") + .setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0") + .setName("Root project") + .setDescription("Root description") + .setBaseDir(moduleBaseDir.toFile()) + .setWorkDir(temp.newFolder()); + DefaultInputModule root = new DefaultInputModule(rootDef, 1); + + moduleHierarchy = mock(InputModuleHierarchy.class); + when(moduleHierarchy.root()).thenReturn(root); + when(moduleHierarchy.children(root)).thenReturn(Collections.emptyList()); + + // dir with changed files + DefaultInputDir dir = new DefaultInputDir("module1", "src", 2) + .setModuleBaseDir(moduleBaseDir); + tree.index(dir, root); + + // dir without changed files or issues + DefaultInputDir dir2 = new DefaultInputDir("module1", "src2", 3) + .setModuleBaseDir(moduleBaseDir); + tree.index(dir2, root); + + // dir without changed files but has issues + DefaultInputDir dir3 = new DefaultInputDir("module1", "src3", 4) + .setModuleBaseDir(moduleBaseDir); + tree.index(dir3, root); + writeIssue(4); + + DefaultInputFile file = new TestInputFileBuilder("module1", "src/Foo.java", 5) + .setLines(2) + .setPublish(true) + .setStatus(InputFile.Status.ADDED) + .build(); + tree.index(file, dir); + + DefaultInputFile file2 = new TestInputFileBuilder("module1", "src2/Foo2.java", 6) + .setPublish(true) + .setStatus(InputFile.Status.SAME) + .setLines(2) + .build(); + tree.index(file2, dir2); + + DefaultInputFile file3 = new TestInputFileBuilder("module1", "src3/Foo3.java", 7) + .setPublish(true) + .setStatus(InputFile.Status.SAME) + .setLines(2) + .build(); + tree.index(file3, dir3); + + ComponentsPublisher publisher = new ComponentsPublisher(moduleHierarchy, tree, branchConfiguration); + publisher.publish(writer); + + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 2)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 4)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 5)).isTrue(); + + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 3)).isFalse(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 6)).isFalse(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 7)).isFalse(); + } + + @Test + public void skip_unchanged_components_in_pull_requests() throws IOException { + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); ProjectAnalysisInfo projectAnalysisInfo = mock(ProjectAnalysisInfo.class); when(projectAnalysisInfo.analysisDate()).thenReturn(DateUtils.parseDate("2012-12-12")); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java index 128561c45bf..b25f773f536 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java @@ -59,6 +59,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST; import static org.sonar.scanner.scan.branch.BranchType.SHORT; public class ReportPublisherTest { @@ -267,4 +268,42 @@ public class ReportPublisherTest { .containsExactlyInAnyOrder("branch=" + branchName, "branchType=" + SHORT.name()); } + @Test + public void send_pull_request_characteristic() throws Exception { + ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class), + new ReportPublisherStep[0], branchConfiguration); + + String orgName = "MyOrg"; + settings.setProperty(ScannerProperties.ORGANIZATION, orgName); + + String branchName = "feature"; + String pullRequestId = "pr-123"; + when(branchConfiguration.branchName()).thenReturn(branchName); + when(branchConfiguration.branchType()).thenReturn(PULL_REQUEST); + when(branchConfiguration.pullRequestKey()).thenReturn(pullRequestId); + + WsResponse response = mock(WsResponse.class); + + PipedOutputStream out = new PipedOutputStream(); + PipedInputStream in = new PipedInputStream(out); + Ce.SubmitResponse.newBuilder().build().writeTo(out); + out.close(); + + when(response.failIfNotSuccessful()).thenReturn(response); + when(response.contentStream()).thenReturn(in); + + when(wsClient.call(any(WsRequest.class))).thenReturn(response); + underTest.upload(temp.newFile()); + + ArgumentCaptor<WsRequest> capture = ArgumentCaptor.forClass(WsRequest.class); + verify(wsClient).call(capture.capture()); + + WsRequest wsRequest = capture.getValue(); + assertThat(wsRequest.getParameters().getKeys()).hasSize(3); + assertThat(wsRequest.getParameters().getValues("organization")).containsExactly(orgName); + assertThat(wsRequest.getParameters().getValues("projectKey")).containsExactly("struts"); + assertThat(wsRequest.getParameters().getValues("characteristic")) + .containsExactlyInAnyOrder("pullRequest=" + pullRequestId); + } + } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisherTest.java index 7b35a9af541..a1175ee2332 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TestExecutionAndCoveragePublisherTest.java @@ -40,7 +40,21 @@ public class TestExecutionAndCoveragePublisherTest { @Test public void do_nothing_for_short_living_branches() throws IOException { BranchConfiguration branchConfiguration = mock(BranchConfiguration.class); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); + InputComponentStore componentStore = mock(InputComponentStore.class); + TestExecutionAndCoveragePublisher publisher = new TestExecutionAndCoveragePublisher(componentStore, null, branchConfiguration); + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + publisher.publish(writer); + + verifyZeroInteractions(componentStore); + } + + @Test + public void do_nothing_for_pull_requests() throws IOException { + BranchConfiguration branchConfiguration = mock(BranchConfiguration.class); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); InputComponentStore componentStore = mock(InputComponentStore.class); TestExecutionAndCoveragePublisher publisher = new TestExecutionAndCoveragePublisher(componentStore, null, branchConfiguration); File outputDir = temp.newFolder(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java index e71a71f161b..0c0799c9ab3 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java @@ -191,6 +191,45 @@ public class ProjectReactorValidatorTest { } @Test + public void fail_when_pull_request_id_specified_but_branch_plugin_not_present() { + ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo"); + ProjectReactor reactor = new ProjectReactor(def); + + when(settings.get(eq(ScannerProperties.PULL_REQUEST_KEY))).thenReturn(Optional.of("#1984")); + + thrown.expect(MessageException.class); + thrown.expectMessage("the branch plugin is required but not installed"); + + validator.validate(reactor); + } + + @Test + public void fail_when_pull_request_branch_is_specified_but_branch_plugin_not_present() { + ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo"); + ProjectReactor reactor = new ProjectReactor(def); + + when(settings.get(eq(ScannerProperties.PULL_REQUEST_BRANCH))).thenReturn(Optional.of("feature1")); + + thrown.expect(MessageException.class); + thrown.expectMessage("the branch plugin is required but not installed"); + + validator.validate(reactor); + } + + @Test + public void fail_when_pull_request_base_specified_but_branch_plugin_not_present() { + ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo"); + ProjectReactor reactor = new ProjectReactor(def); + + when(settings.get(eq(ScannerProperties.PULL_REQUEST_BASE))).thenReturn(Optional.of("feature1")); + + thrown.expect(MessageException.class); + thrown.expectMessage("the branch plugin is required but not installed"); + + validator.validate(reactor); + } + + @Test public void not_fail_with_valid_version() { validator.validate(createProjectReactor("foo", def -> def.setVersion("1.0"))); validator.validate(createProjectReactor("foo", def -> def.setVersion("2017-10-16"))); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/BranchConfigurationProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/BranchConfigurationProviderTest.java index 067114dd43b..a8fbc75cc8b 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/BranchConfigurationProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/BranchConfigurationProviderTest.java @@ -41,6 +41,7 @@ public class BranchConfigurationProviderTest { private BranchConfigurationLoader loader; private BranchConfiguration config; private ProjectBranches branches; + private ProjectPullRequests pullRequests; private ProjectKey projectKey; private Map<String, String> globalPropertiesMap; private Map<String, String> remoteProjectSettings; @@ -52,6 +53,7 @@ public class BranchConfigurationProviderTest { loader = mock(BranchConfigurationLoader.class); config = mock(BranchConfiguration.class); branches = mock(ProjectBranches.class); + pullRequests = mock(ProjectPullRequests.class); settingsLoader = mock(SettingsLoader.class); projectKey = mock(ProjectKey.class); globalPropertiesMap = new HashMap<>(); @@ -61,22 +63,24 @@ public class BranchConfigurationProviderTest { @Test public void should_cache_config() { - BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches); - assertThat(provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches)).isSameAs(configuration); + BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests); + assertThat(provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests)).isSameAs(configuration); } @Test public void should_use_loader() { - when(loader.load(eq(globalPropertiesMap), any(Supplier.class), eq(branches))).thenReturn(config); - BranchConfiguration branchConfig = provider.provide(loader, globalConfiguration, projectKey, settingsLoader, branches); + when(loader.load(eq(globalPropertiesMap), any(Supplier.class), eq(branches), eq(pullRequests))).thenReturn(config); - assertThat(branchConfig).isSameAs(config); + BranchConfiguration result = provider.provide(loader, globalConfiguration, projectKey, settingsLoader, branches, pullRequests); + + assertThat(result).isSameAs(config); } @Test public void should_return_default_if_no_loader() { - BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches); - assertThat(configuration.branchTarget()).isNull(); - assertThat(configuration.branchType()).isEqualTo(BranchType.LONG); + BranchConfiguration result = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests); + + assertThat(result.branchTarget()).isNull(); + assertThat(result.branchType()).isEqualTo(BranchType.LONG); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java index 6b75d766e3e..fd22c4d96e8 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectBranchesProviderTest.java @@ -19,11 +19,8 @@ */ package org.sonar.scanner.scan.branch; -import java.util.Optional; import org.junit.Before; import org.junit.Test; -import org.sonar.api.config.Configuration; -import org.sonar.scanner.bootstrap.GlobalConfiguration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; @@ -34,33 +31,30 @@ public class ProjectBranchesProviderTest { private ProjectBranchesProvider provider = new ProjectBranchesProvider(); private ProjectBranchesLoader mockLoader; private ProjectBranches mockBranches; - private GlobalConfiguration mockSettings; @Before public void setUp() { mockLoader = mock(ProjectBranchesLoader.class); mockBranches = mock(ProjectBranches.class); - mockSettings = mock(GlobalConfiguration.class); } @Test public void should_cache_branches() { - ProjectBranches branches = provider.provide(null, () -> "project", mockSettings); - assertThat(provider.provide(null, () -> "project", mockSettings)).isSameAs(branches); + ProjectBranches branches = provider.provide(null, () -> "project"); + assertThat(provider.provide(null, () -> "project")).isSameAs(branches); } @Test public void should_use_loader() { when(mockLoader.load("key")).thenReturn(mockBranches); - when(mockSettings.get(anyString())).thenReturn(Optional.of("somebranch")); - ProjectBranches branches = provider.provide(mockLoader, () -> "key", mockSettings); + ProjectBranches branches = provider.provide(mockLoader, () -> "key"); assertThat(branches).isSameAs(mockBranches); } @Test public void should_return_default_if_no_loader() { - ProjectBranches branches = provider.provide(null, () -> "project", mockSettings); + ProjectBranches branches = provider.provide(null, () -> "project"); assertThat(branches.isEmpty()).isTrue(); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProviderTest.java new file mode 100644 index 00000000000..b3c1ab87fd7 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/branch/ProjectPullRequestsProviderTest.java @@ -0,0 +1,63 @@ +/* + * 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.scanner.scan.branch; + +import org.junit.Before; +import org.junit.Test; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProjectPullRequestsProviderTest { + private ProjectPullRequestsProvider provider = new ProjectPullRequestsProvider(); + private ProjectPullRequestsLoader mockLoader; + private ProjectPullRequests pullRequests; + + @Before + public void setUp() { + mockLoader = mock(ProjectPullRequestsLoader.class); + pullRequests = new ProjectPullRequests(emptyList()); + } + + @Test + public void cache_pull_requests() { + ProjectPullRequests pullRequests = provider.provide(null, () -> "project"); + + assertThat(provider.provide(null, () -> "project")).isSameAs(pullRequests); + } + + @Test + public void should_use_loader() { + when(mockLoader.load("key")).thenReturn(pullRequests); + + ProjectPullRequests result = provider.provide(mockLoader, () -> "key"); + + assertThat(result).isSameAs(pullRequests); + } + + @Test + public void should_return_default_if_no_loader() { + ProjectPullRequests result = provider.provide(null, () -> "project"); + + assertThat(result.isEmpty()).isTrue(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java index 83db811b6b9..bc212aa9489 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scm/ScmChangedFilesProviderTest.java @@ -66,7 +66,7 @@ public class ScmChangedFilesProviderTest { @Test public void testNoScmProvider() { - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(branchConfiguration.branchTarget()).thenReturn("target"); ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); @@ -78,7 +78,7 @@ public class ScmChangedFilesProviderTest { @Test public void testFailIfRelativePath() { when(branchConfiguration.branchTarget()).thenReturn("target"); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(scmConfiguration.provider()).thenReturn(scmProvider); when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singleton(Paths.get("changedFile"))); @@ -90,7 +90,7 @@ public class ScmChangedFilesProviderTest { @Test public void testProviderDoesntSupport() { when(branchConfiguration.branchTarget()).thenReturn("target"); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(scmConfiguration.provider()).thenReturn(scmProvider); when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(null); ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); @@ -101,7 +101,7 @@ public class ScmChangedFilesProviderTest { @Test public void testNoOpInNonShortLivedBranch() { - when(branchConfiguration.isShortLivingBranch()).thenReturn(false); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(false); ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); assertThat(scmChangedFiles.get()).isNull(); @@ -118,7 +118,7 @@ public class ScmChangedFilesProviderTest { }; when(scmConfiguration.provider()).thenReturn(legacy); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(branchConfiguration.branchTarget()).thenReturn("target"); ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); @@ -130,7 +130,7 @@ public class ScmChangedFilesProviderTest { @Test public void testReturnChangedFiles() { when(branchConfiguration.branchTarget()).thenReturn("target"); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); when(scmConfiguration.provider()).thenReturn(scmProvider); when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singleton(Paths.get("changedFile").toAbsolutePath())); ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); @@ -143,7 +143,7 @@ public class ScmChangedFilesProviderTest { public void testCacheObject() { provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy); - verify(branchConfiguration).isShortLivingBranch(); + verify(branchConfiguration).isShortOrPullRequest(); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java index d09769ee3a8..68771fe5059 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java @@ -91,7 +91,14 @@ public class DefaultSensorContextTest { @Test public void shouldSkipDupsAndCoverageOnShortBranches() { - when(branchConfig.isShortLivingBranch()).thenReturn(true); + when(branchConfig.isShortOrPullRequest()).thenReturn(true); + assertThat(adaptor.newCpdTokens()).isEqualTo(DefaultSensorContext.NO_OP_NEW_CPD_TOKENS); + assertThat(adaptor.newCoverage()).isEqualTo(DefaultSensorContext.NO_OP_NEW_COVERAGE); + } + + @Test + public void shouldSkipDupsAndCoverageOnPullRequests() { + when(branchConfig.isShortOrPullRequest()).thenReturn(true); assertThat(adaptor.newCpdTokens()).isEqualTo(DefaultSensorContext.NO_OP_NEW_CPD_TOKENS); assertThat(adaptor.newCoverage()).isEqualTo(DefaultSensorContext.NO_OP_NEW_COVERAGE); } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java index 52da53edbf5..843e4730b45 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java @@ -127,7 +127,7 @@ public class DefaultSensorStorageTest { @Test public void should_skip_issue_on_short_branch_when_file_status_is_SAME() { InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build(); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); DefaultIssue issue = new DefaultIssue().at(new DefaultIssueLocation().on(file)); underTest.store(issue); @@ -151,7 +151,7 @@ public class DefaultSensorStorageTest { DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php") .setContents("// comment") .setStatus(InputFile.Status.SAME).build(); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); DefaultHighlighting highlighting = new DefaultHighlighting(underTest).onFile(file).highlight(0, 1, TypeOfText.KEYWORD); underTest.store(highlighting); @@ -178,7 +178,20 @@ public class DefaultSensorStorageTest { @Test public void should_skip_file_measure_on_short_branch_when_file_status_is_SAME() { InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build(); - when(branchConfiguration.isShortLivingBranch()).thenReturn(true); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); + + underTest.store(new DefaultMeasure() + .on(file) + .forMetric(CoreMetrics.NCLOC) + .withValue(10)); + + verifyZeroInteractions(measureCache); + } + + @Test + public void should_skip_file_measure_on_pull_request_when_file_status_is_SAME() { + InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build(); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); underTest.store(new DefaultMeasure() .on(file) diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index 7d3123d24c1..c99b9c9882e 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -46,6 +46,8 @@ message Metadata { string relative_path_from_scm_root = 12; string scm_revision_id = 13; + string pull_request_key = 14; + message QProfile { string key = 1; string name = 2; @@ -62,6 +64,7 @@ message Metadata { UNSET = 0; LONG = 1; SHORT = 2; + PULL_REQUEST = 3; } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java index 3c5e64ec06a..bdbbbf90c62 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/DefaultWsClient.java @@ -45,8 +45,9 @@ import org.sonarqube.ws.client.profiles.ProfilesService; import org.sonarqube.ws.client.projectanalyses.ProjectAnalysesService; import org.sonarqube.ws.client.projectbranches.ProjectBranchesService; import org.sonarqube.ws.client.projectlinks.ProjectLinksService; -import org.sonarqube.ws.client.projecttags.ProjectTagsService; +import org.sonarqube.ws.client.projectpullrequests.ProjectPullRequestsService; import org.sonarqube.ws.client.projects.ProjectsService; +import org.sonarqube.ws.client.projecttags.ProjectTagsService; import org.sonarqube.ws.client.properties.PropertiesService; import org.sonarqube.ws.client.qualitygates.QualitygatesService; import org.sonarqube.ws.client.qualityprofiles.QualityprofilesService; @@ -102,6 +103,7 @@ class DefaultWsClient implements WsClient { private final ProjectAnalysesService projectAnalysesService; private final ProjectBranchesService projectBranchesService; private final ProjectLinksService projectLinksService; + private final ProjectPullRequestsService projectPullRequestsService; private final ProjectTagsService projectTagsService; private final ProjectsService projectsService; private final PropertiesService propertiesService; @@ -152,6 +154,7 @@ class DefaultWsClient implements WsClient { this.projectAnalysesService = new ProjectAnalysesService(wsConnector); this.projectBranchesService = new ProjectBranchesService(wsConnector); this.projectLinksService = new ProjectLinksService(wsConnector); + this.projectPullRequestsService = new ProjectPullRequestsService(wsConnector); this.projectTagsService = new ProjectTagsService(wsConnector); this.projectsService = new ProjectsService(wsConnector); this.propertiesService = new PropertiesService(wsConnector); @@ -303,6 +306,11 @@ class DefaultWsClient implements WsClient { } @Override + public ProjectPullRequestsService projectPullRequests() { + return projectPullRequestsService; + } + + @Override public ProjectTagsService projectTags() { return projectTagsService; } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java index 570b96cef47..79ca15098c2 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java @@ -45,6 +45,7 @@ import org.sonarqube.ws.client.profiles.ProfilesService; import org.sonarqube.ws.client.projectanalyses.ProjectAnalysesService; import org.sonarqube.ws.client.projectbranches.ProjectBranchesService; import org.sonarqube.ws.client.projectlinks.ProjectLinksService; +import org.sonarqube.ws.client.projectpullrequests.ProjectPullRequestsService; import org.sonarqube.ws.client.projecttags.ProjectTagsService; import org.sonarqube.ws.client.projects.ProjectsService; import org.sonarqube.ws.client.properties.PropertiesService; @@ -139,6 +140,8 @@ public interface WsClient { ProjectLinksService projectLinks(); + ProjectPullRequestsService projectPullRequests(); + ProjectTagsService projectTags(); ProjectsService projects(); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java index 5e726b0eafb..f6fdf792115 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java @@ -39,6 +39,7 @@ public class ComponentsWsParameters { public static final String PARAM_COMPONENT_ID = "componentId"; public static final String PARAM_COMPONENT = "component"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; private ComponentsWsParameters() { // static utility class diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/AppRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/AppRequest.java index 772651aa090..14441f113b9 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/AppRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/AppRequest.java @@ -34,6 +34,7 @@ public class AppRequest { private String branch; private String component; private String componentId; + private String pullRequest; /** * This is part of the internal API. @@ -73,4 +74,17 @@ public class AppRequest { public String getComponentId() { return componentId; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public AppRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ComponentsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ComponentsService.java index 483474301b1..bc8d556b033 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ComponentsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ComponentsService.java @@ -55,6 +55,7 @@ public class ComponentsService extends BaseService { .setParam("branch", request.getBranch()) .setParam("component", request.getComponent()) .setParam("componentId", request.getComponentId()) + .setParam("pullRequest", request.getPullRequest()) .setMediaType(MediaTypes.JSON) ).content(); } @@ -111,7 +112,8 @@ public class ComponentsService extends BaseService { new GetRequest(path("show")) .setParam("branch", request.getBranch()) .setParam("component", request.getComponent()) - .setParam("componentId", request.getComponentId()), + .setParam("componentId", request.getComponentId()) + .setParam("pullRequest", request.getPullRequest()), ShowWsResponse.parser()); } @@ -147,6 +149,7 @@ public class ComponentsService extends BaseService { .setParam("componentId", request.getComponentId()) .setParam("p", request.getP()) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("q", request.getQ()) .setParam("qualifiers", request.getQualifiers() == null ? null : request.getQualifiers().stream().collect(Collectors.joining(","))) .setParam("s", request.getS() == null ? null : request.getS().stream().collect(Collectors.joining(","))) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ShowRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ShowRequest.java index b6306545598..4a04cd56d1b 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ShowRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/ShowRequest.java @@ -34,6 +34,7 @@ public class ShowRequest { private String branch; private String component; private String componentId; + private String pullRequest; /** * This is part of the internal API. @@ -73,4 +74,17 @@ public class ShowRequest { public String getComponentId() { return componentId; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ShowRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/TreeRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/TreeRequest.java index 4aae16c2d9c..82ae76cb9a6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/components/TreeRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/components/TreeRequest.java @@ -37,6 +37,7 @@ public class TreeRequest { private String componentId; private String p; private String ps; + private String pullRequest; private String q; private List<String> qualifiers; private List<String> s; @@ -124,6 +125,19 @@ public class TreeRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public TreeRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "FILE_NAM" */ public TreeRequest setQ(String q) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/DuplicationsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/DuplicationsService.java index de57a61729d..dc68920fc3d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/DuplicationsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/DuplicationsService.java @@ -50,6 +50,7 @@ public class DuplicationsService extends BaseService { new GetRequest(path("show")) .setParam("branch", request.getBranch()) .setParam("key", request.getKey()) + .setParam("pullRequest", request.getPullRequest()) .setParam("uuid", request.getUuid()), ShowResponse.parser()); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/ShowRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/ShowRequest.java index 0ee02965ce5..333618abb66 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/ShowRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/duplications/ShowRequest.java @@ -33,6 +33,7 @@ public class ShowRequest { private String branch; private String key; + private String pullRequest; private String uuid; /** @@ -61,6 +62,19 @@ public class ShowRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public ShowRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "584a89f2-8037-4f7b-b82c-8b45d2d63fb2" * @deprecated since 6.5 */ diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java index 3dbaa54c9fd..0032e024f4d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java @@ -70,6 +70,7 @@ public class IssuesWsParameters { public static final String PARAM_FILE_UUIDS = "fileUuids"; public static final String PARAM_ON_COMPONENT_ONLY = "onComponentOnly"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; public static final String PARAM_ORGANIZATION = "organization"; public static final String PARAM_RULES = "rules"; public static final String PARAM_ACTIONS = "actions"; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java index 28a7ebe3775..75918c48fad 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java @@ -232,6 +232,7 @@ public class IssuesService extends BaseService { .setParam("projectUuids", request.getProjectUuids() == null ? null : request.getProjectUuids().stream().collect(Collectors.joining(","))) .setParam("projects", request.getProjects() == null ? null : request.getProjects().stream().collect(Collectors.joining(","))) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("resolutions", request.getResolutions() == null ? null : request.getResolutions().stream().collect(Collectors.joining(","))) .setParam("resolved", request.getResolved()) .setParam("rules", request.getRules() == null ? null : request.getRules().stream().collect(Collectors.joining(","))) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/SearchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/SearchRequest.java index 3e5def5f536..74096bdde58 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/SearchRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issues/SearchRequest.java @@ -59,6 +59,7 @@ public class SearchRequest { private List<String> projectUuids; private List<String> projects; private String ps; + private String pullRequest; private List<String> resolutions; private String resolved; private List<String> rules; @@ -470,6 +471,19 @@ public class SearchRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public SearchRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "FIXED,REMOVED" * Possible values: * <ul> diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentRequest.java index 5029fb0e572..fa8041a095d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentRequest.java @@ -38,6 +38,7 @@ public class ComponentRequest { private String developerId; private String developerKey; private List<String> metricKeys; + private String pullRequest; /** * Example value: "periods,metrics" @@ -133,4 +134,17 @@ public class ComponentRequest { public List<String> getMetricKeys() { return metricKeys; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ComponentRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentTreeRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentTreeRequest.java index 8799bf1bc5c..d9416aa81c9 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentTreeRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/ComponentTreeRequest.java @@ -44,6 +44,7 @@ public class ComponentTreeRequest { private String metricSortFilter; private String p; private String ps; + private String pullRequest; private String q; private List<String> qualifiers; private List<String> s; @@ -230,6 +231,19 @@ public class ComponentTreeRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public ComponentTreeRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "FILE_NAM" */ public ComponentTreeRequest setQ(String q) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/MeasuresService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/MeasuresService.java index 06e82e95def..f9662d04ace 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/MeasuresService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/MeasuresService.java @@ -57,7 +57,8 @@ public class MeasuresService extends BaseService { .setParam("componentId", request.getComponentId()) .setParam("developerId", request.getDeveloperId()) .setParam("developerKey", request.getDeveloperKey()) - .setParam("metricKeys", request.getMetricKeys() == null ? null : request.getMetricKeys().stream().collect(Collectors.joining(","))), + .setParam("metricKeys", request.getMetricKeys() == null ? null : request.getMetricKeys().stream().collect(Collectors.joining(","))) + .setParam("pullRequest", request.getPullRequest()), ComponentWsResponse.parser()); } @@ -84,6 +85,7 @@ public class MeasuresService extends BaseService { .setParam("metricSortFilter", request.getMetricSortFilter()) .setParam("p", request.getP()) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("q", request.getQ()) .setParam("qualifiers", request.getQualifiers() == null ? null : request.getQualifiers().stream().collect(Collectors.joining(","))) .setParam("s", request.getS() == null ? null : request.getS().stream().collect(Collectors.joining(","))) @@ -122,6 +124,7 @@ public class MeasuresService extends BaseService { .setParam("metrics", request.getMetrics() == null ? null : request.getMetrics().stream().collect(Collectors.joining(","))) .setParam("p", request.getP()) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("to", request.getTo()), SearchHistoryResponse.parser()); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/SearchHistoryRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/SearchHistoryRequest.java index 37f2b02da89..04395aff33a 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/SearchHistoryRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measures/SearchHistoryRequest.java @@ -37,6 +37,7 @@ public class SearchHistoryRequest { private List<String> metrics; private String p; private String ps; + private String pullRequest; private String to; /** @@ -115,6 +116,19 @@ public class SearchHistoryRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public SearchHistoryRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "2017-10-19 or 2017-10-19T13:00:00+0200" */ public SearchHistoryRequest setTo(String to) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/ComponentRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/ComponentRequest.java index 5dc8aa75360..a442f0092c7 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/ComponentRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/ComponentRequest.java @@ -33,6 +33,7 @@ public class ComponentRequest { private String branch; private String component; + private String pullRequest; /** * This is part of the internal API. @@ -58,4 +59,17 @@ public class ComponentRequest { public String getComponent() { return component; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ComponentRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/NavigationService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/NavigationService.java index 7dcbc5090f4..0b8edf6a5b4 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/NavigationService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/navigation/NavigationService.java @@ -49,6 +49,7 @@ public class NavigationService extends BaseService { new GetRequest(path("component")) .setParam("branch", request.getBranch()) .setParam("component", request.getComponent()) + .setParam("pullRequest", request.getPullRequest()) .setMediaType(MediaTypes.JSON) ).content(); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/permissions/TemplateGroupsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/permissions/TemplateGroupsRequest.java index 5388364e035..04d9b272bce 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/permissions/TemplateGroupsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/permissions/TemplateGroupsRequest.java @@ -65,7 +65,6 @@ public class TemplateGroupsRequest { } /** - * This is a mandatory parameter. * Possible values: * <ul> * <li>"admin"</li> diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/ProjectAnalysesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/ProjectAnalysesService.java index 3166e6f8215..10ab3a2efba 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/ProjectAnalysesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/ProjectAnalysesService.java @@ -102,6 +102,7 @@ public class ProjectAnalysesService extends BaseService { .setParam("p", request.getP()) .setParam("project", request.getProject()) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("to", request.getTo()), SearchResponse.parser()); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/SearchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/SearchRequest.java index 83e00959d0e..1f81fa87296 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/SearchRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalyses/SearchRequest.java @@ -37,6 +37,7 @@ public class SearchRequest { private String p; private String project; private String ps; + private String pullRequest; private String to; /** @@ -121,6 +122,19 @@ public class SearchRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public SearchRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "2017-10-19 or 2017-10-19T13:00:00+0200" */ public SearchRequest setTo(String to) { 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 1a65b559e8b..9eca20837c9 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 @@ -45,8 +45,8 @@ public class ProjectBranchesService extends BaseService { * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_branches/delete">Further information about this action online (including a response example)</a> * @since 6.6 */ - public String delete(DeleteRequest request) { - return call( + public void delete(DeleteRequest request) { + call( new PostRequest(path("delete")) .setParam("branch", request.getBranch()) .setParam("project", request.getProject()) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/DeleteRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/DeleteRequest.java new file mode 100644 index 00000000000..aaaf8869bf7 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/DeleteRequest.java @@ -0,0 +1,62 @@ +/* + * 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.sonarqube.ws.client.projectpullrequests; + +import java.util.List; +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/delete">Further information about this action online (including a response example)</a> + * @since 7.1 + */ +@Generated("sonar-ws-generator") +public class DeleteRequest { + + private String project; + private String pullRequest; + + /** + * This is a mandatory parameter. + * Example value: "my_project" + */ + public DeleteRequest setProject(String project) { + this.project = project; + return this; + } + + public String getProject() { + return project; + } + + /** + * This is a mandatory parameter. + * Example value: "1543" + */ + public DeleteRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ListRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ListRequest.java new file mode 100644 index 00000000000..fe7d93ef0bd --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ListRequest.java @@ -0,0 +1,48 @@ +/* + * 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.sonarqube.ws.client.projectpullrequests; + +import java.util.List; +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/list">Further information about this action online (including a response example)</a> + * @since 7.1 + */ +@Generated("sonar-ws-generator") +public class ListRequest { + + private String project; + + /** + * This is a mandatory parameter. + * Example value: "my_project" + */ + public ListRequest setProject(String project) { + this.project = project; + return this; + } + + public String getProject() { + return project; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ProjectPullRequestsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ProjectPullRequestsService.java new file mode 100644 index 00000000000..b7ba3dabc18 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/ProjectPullRequestsService.java @@ -0,0 +1,70 @@ +/* + * 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.sonarqube.ws.client.projectpullrequests; + +import java.util.stream.Collectors; +import javax.annotation.Generated; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.client.BaseService; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsConnector; +import org.sonarqube.ws.ProjectPullRequests.ListWsResponse; + +/** + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests">Further information about this web service online</a> + */ +@Generated("sonar-ws-generator") +public class ProjectPullRequestsService extends BaseService { + + public ProjectPullRequestsService(WsConnector wsConnector) { + super(wsConnector, "api/project_pull_requests"); + } + + /** + * + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/delete">Further information about this action online (including a response example)</a> + * @since 7.1 + */ + public void delete(DeleteRequest request) { + call( + new PostRequest(path("delete")) + .setParam("project", request.getProject()) + .setParam("pullRequest", request.getPullRequest()) + .setMediaType(MediaTypes.JSON) + ).content(); + } + + /** + * + * This is part of the internal API. + * This is a GET request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/list">Further information about this action online (including a response example)</a> + * @since 7.1 + */ + public ListWsResponse list(ListRequest request) { + return call( + new GetRequest(path("list")) + .setParam("project", request.getProject()), + ListWsResponse.parser()); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/package-info.java new file mode 100644 index 00000000000..e742bab1d5e --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectpullrequests/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +@Generated("sonar-ws-generator") +package org.sonarqube.ws.client.projectpullrequests; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.Generated; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CopyRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CopyRequest.java index dffd09b323a..c0615b16482 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CopyRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CopyRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateConditionRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateConditionRequest.java index ebc887524f4..0ef25a4c881 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateConditionRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateConditionRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateRequest.java index 31ba90f154f..0fb572e240b 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/CreateRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeleteConditionRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeleteConditionRequest.java index 6f9c7d6a308..d85bf386d0f 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeleteConditionRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeleteConditionRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeselectRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeselectRequest.java index f3ea2174093..2b7603aef0a 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeselectRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DeselectRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DestroyRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DestroyRequest.java index 3e2169d78d1..89c817d1863 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DestroyRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/DestroyRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/GetByProjectRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/GetByProjectRequest.java index f07bc686d31..809241b5fe4 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/GetByProjectRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/GetByProjectRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ListRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ListRequest.java index 2f998f79002..01b75ec4e47 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ListRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ListRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ProjectStatusRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ProjectStatusRequest.java index 64272965eed..557d4f53bf6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ProjectStatusRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ProjectStatusRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/QualitygatesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/QualitygatesService.java index 662524360a7..1c65c9e9181 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/QualitygatesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/QualitygatesService.java @@ -19,20 +19,21 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.stream.Collectors; import javax.annotation.Generated; import org.sonarqube.ws.MediaTypes; -import org.sonarqube.ws.Qualitygates.CreateConditionResponse; +import org.sonarqube.ws.client.BaseService; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsConnector; import org.sonarqube.ws.Qualitygates.CreateResponse; +import org.sonarqube.ws.Qualitygates.CreateConditionResponse; import org.sonarqube.ws.Qualitygates.GetByProjectResponse; import org.sonarqube.ws.Qualitygates.ListWsResponse; import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; import org.sonarqube.ws.Qualitygates.SearchResponse; import org.sonarqube.ws.Qualitygates.ShowWsResponse; import org.sonarqube.ws.Qualitygates.UpdateConditionResponse; -import org.sonarqube.ws.client.BaseService; -import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.PostRequest; -import org.sonarqube.ws.client.WsConnector; /** * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/qualitygates">Further information about this web service online</a> diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/RenameRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/RenameRequest.java index f08636bda92..d98af61c139 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/RenameRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/RenameRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SearchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SearchRequest.java index ec39eb47fff..a7bcf5b94bd 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SearchRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SearchRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SelectRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SelectRequest.java index 146cda0c09e..e5bd51005c5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SelectRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SelectRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SetAsDefaultRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SetAsDefaultRequest.java index 7bdc088ec18..2692abf157c 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SetAsDefaultRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/SetAsDefaultRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ShowRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ShowRequest.java index 5bdcfa8214f..b36b8a4e2f5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ShowRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/ShowRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/UpdateConditionRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/UpdateConditionRequest.java index 77934588782..8435549d125 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/UpdateConditionRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualitygates/UpdateConditionRequest.java @@ -19,6 +19,7 @@ */ package org.sonarqube.ws.client.qualitygates; +import java.util.List; import javax.annotation.Generated; /** diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/rules/SearchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/rules/SearchRequest.java index 0a15f7f8500..bb974145015 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/rules/SearchRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/rules/SearchRequest.java @@ -162,6 +162,7 @@ public class SearchRequest { * <li>"remFn"</li> * <li>"remFnOverloaded"</li> * <li>"repo"</li> + * <li>"scope"</li> * <li>"severity"</li> * <li>"status"</li> * <li>"sysTags"</li> diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ListDefinitionsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ListDefinitionsRequest.java index 97859d258cd..716f8c45347 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ListDefinitionsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ListDefinitionsRequest.java @@ -33,6 +33,7 @@ public class ListDefinitionsRequest { private String branch; private String component; + private String pullRequest; /** * This is part of the internal API. @@ -58,4 +59,17 @@ public class ListDefinitionsRequest { public String getComponent() { return component; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ListDefinitionsRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ResetRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ResetRequest.java index 8d47493a4b9..6f1e6d771da 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ResetRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ResetRequest.java @@ -34,6 +34,7 @@ public class ResetRequest { private String branch; private String component; private List<String> keys; + private String pullRequest; /** * This is part of the internal API. @@ -72,4 +73,17 @@ public class ResetRequest { public List<String> getKeys() { return keys; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ResetRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SetRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SetRequest.java index ef5da8de676..819d0bf0d8b 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SetRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SetRequest.java @@ -35,6 +35,7 @@ public class SetRequest { private String component; private List<String> fieldValues; private String key; + private String pullRequest; private String value; private List<String> values; @@ -89,6 +90,19 @@ public class SetRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public SetRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "git@github.com:SonarSource/sonarqube.git" */ public SetRequest setValue(String value) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SettingsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SettingsService.java index f920dbb7a1a..d3b64a5579e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SettingsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/SettingsService.java @@ -93,7 +93,8 @@ public class SettingsService extends BaseService { return call( new GetRequest(path("list_definitions")) .setParam("branch", request.getBranch()) - .setParam("component", request.getComponent()), + .setParam("component", request.getComponent()) + .setParam("pullRequest", request.getPullRequest()), ListDefinitionsWsResponse.parser()); } @@ -110,6 +111,7 @@ public class SettingsService extends BaseService { .setParam("branch", request.getBranch()) .setParam("component", request.getComponent()) .setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(","))) + .setParam("pullRequest", request.getPullRequest()) .setMediaType(MediaTypes.JSON) ).content(); } @@ -128,6 +130,7 @@ public class SettingsService extends BaseService { .setParam("component", request.getComponent()) .setParam("fieldValues", request.getFieldValues() == null ? null : request.getFieldValues()) .setParam("key", request.getKey()) + .setParam("pullRequest", request.getPullRequest()) .setParam("value", request.getValue()) .setParam("values", request.getValues() == null ? null : request.getValues()) .setMediaType(MediaTypes.JSON) @@ -146,7 +149,8 @@ public class SettingsService extends BaseService { new GetRequest(path("values")) .setParam("branch", request.getBranch()) .setParam("component", request.getComponent()) - .setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(","))), + .setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(","))) + .setParam("pullRequest", request.getPullRequest()), ValuesWsResponse.parser()); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ValuesRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ValuesRequest.java index 3d7da5ea1cd..be37e21c8d0 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ValuesRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/settings/ValuesRequest.java @@ -34,6 +34,7 @@ public class ValuesRequest { private String branch; private String component; private List<String> keys; + private String pullRequest; /** * This is part of the internal API. @@ -71,4 +72,17 @@ public class ValuesRequest { public List<String> getKeys() { return keys; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public ValuesRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/LinesRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/LinesRequest.java index c100ee46691..00848510f63 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/LinesRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/LinesRequest.java @@ -34,6 +34,7 @@ public class LinesRequest { private String branch; private String from; private String key; + private String pullRequest; private String to; private String uuid; @@ -75,6 +76,19 @@ public class LinesRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public LinesRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "20" */ public LinesRequest setTo(String to) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/RawRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/RawRequest.java index 7fccf661dbf..5a7652b6717 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/RawRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/RawRequest.java @@ -33,6 +33,7 @@ public class RawRequest { private String branch; private String key; + private String pullRequest; /** * This is part of the internal API. @@ -59,4 +60,17 @@ public class RawRequest { public String getKey() { return key; } + + /** + * This is part of the internal API. + * Example value: "5461" + */ + public RawRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/SourcesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/SourcesService.java index 37795a4e13c..f8fe1a17b1e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/SourcesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/sources/SourcesService.java @@ -82,6 +82,7 @@ public class SourcesService extends BaseService { .setParam("branch", request.getBranch()) .setParam("from", request.getFrom()) .setParam("key", request.getKey()) + .setParam("pullRequest", request.getPullRequest()) .setParam("to", request.getTo()) .setParam("uuid", request.getUuid()) .setMediaType(MediaTypes.JSON) @@ -100,6 +101,7 @@ public class SourcesService extends BaseService { new GetRequest(path("raw")) .setParam("branch", request.getBranch()) .setParam("key", request.getKey()) + .setParam("pullRequest", request.getPullRequest()) .setMediaType(MediaTypes.JSON) ).content(); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/ListRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/ListRequest.java index 6e445f06c85..a7a38ca8cd6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/ListRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/ListRequest.java @@ -34,6 +34,7 @@ public class ListRequest { private String branch; private String p; private String ps; + private String pullRequest; private String sourceFileId; private String sourceFileKey; private String sourceFileLineNumber; @@ -79,6 +80,19 @@ public class ListRequest { } /** + * This is part of the internal API. + * Example value: "5461" + */ + public ListRequest setPullRequest(String pullRequest) { + this.pullRequest = pullRequest; + return this; + } + + public String getPullRequest() { + return pullRequest; + } + + /** * Example value: "AU-TpxcA-iU5OvuD2FL0" */ public ListRequest setSourceFileId(String sourceFileId) { diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/TestsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/TestsService.java index 2552c9a1a99..3683b5d79c7 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/TestsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/tests/TestsService.java @@ -72,6 +72,7 @@ public class TestsService extends BaseService { .setParam("branch", request.getBranch()) .setParam("p", request.getP()) .setParam("ps", request.getPs()) + .setParam("pullRequest", request.getPullRequest()) .setParam("sourceFileId", request.getSourceFileId()) .setParam("sourceFileKey", request.getSourceFileKey()) .setParam("sourceFileLineNumber", request.getSourceFileLineNumber()) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetHomepageRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetHomepageRequest.java new file mode 100644 index 00000000000..8ad79cc9590 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetHomepageRequest.java @@ -0,0 +1,67 @@ +/* + * 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.sonarqube.ws.client.users; + +import java.util.List; +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_homepage">Further information about this action online (including a response example)</a> + * @since 7.0 + */ +@Generated("sonar-ws-generator") +public class SetHomepageRequest { + + private String parameter; + private String type; + + /** + * Example value: "my_project" + */ + public SetHomepageRequest setParameter(String parameter) { + this.parameter = parameter; + return this; + } + + public String getParameter() { + return parameter; + } + + /** + * This is a mandatory parameter. + * Possible values: + * <ul> + * <li>"PROJECT"</li> + * <li>"ORGANIZATION"</li> + * <li>"MY_PROJECTS"</li> + * <li>"MY_ISSUES"</li> + * </ul> + */ + public SetHomepageRequest setType(String type) { + this.type = type; + return this; + } + + public String getType() { + return type; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java index c490b22eeb0..3dcec5eaa39 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java @@ -160,6 +160,22 @@ public class UsersService extends BaseService { * * This is part of the internal API. * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_homepage">Further information about this action online (including a response example)</a> + * @since 7.0 + */ + public void setHomepage(SetHomepageRequest request) { + call( + new PostRequest(path("set_homepage")) + .setParam("parameter", request.getParameter()) + .setParam("type", request.getType()) + .setMediaType(MediaTypes.JSON) + ).content(); + } + + /** + * + * This is part of the internal API. + * This is a POST request. * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/skip_onboarding_tutorial">Further information about this action online (including a response example)</a> * @since 6.5 */ diff --git a/sonar-ws/src/main/protobuf/ws-ce.proto b/sonar-ws/src/main/protobuf/ws-ce.proto index dc18f7cb87e..5787a326e5e 100644 --- a/sonar-ws/src/main/protobuf/ws-ce.proto +++ b/sonar-ws/src/main/protobuf/ws-ce.proto @@ -92,6 +92,8 @@ message Task { optional string branch = 21; optional sonarqube.ws.commons.BranchType branchType = 22; optional string errorType = 23; + optional string pullRequest = 24; + optional string pullRequestTitle = 25; } enum TaskStatus { diff --git a/sonar-ws/src/main/protobuf/ws-commons.proto b/sonar-ws/src/main/protobuf/ws-commons.proto index b5b0167e8d0..947e2487657 100644 --- a/sonar-ws/src/main/protobuf/ws-commons.proto +++ b/sonar-ws/src/main/protobuf/ws-commons.proto @@ -127,4 +127,5 @@ enum BranchType { LONG = 1; SHORT = 2; + PULL_REQUEST = 3; } diff --git a/sonar-ws/src/main/protobuf/ws-components.proto b/sonar-ws/src/main/protobuf/ws-components.proto index 73059133649..a995eb42cbc 100644 --- a/sonar-ws/src/main/protobuf/ws-components.proto +++ b/sonar-ws/src/main/protobuf/ws-components.proto @@ -122,6 +122,7 @@ message Component { optional string project = 17; optional string branch = 18; optional string version = 19; + optional string pullRequest = 20; message Tags { repeated string tags = 1; diff --git a/sonar-ws/src/main/protobuf/ws-duplications.proto b/sonar-ws/src/main/protobuf/ws-duplications.proto index 6eeb5f29a32..98cbe46885c 100644 --- a/sonar-ws/src/main/protobuf/ws-duplications.proto +++ b/sonar-ws/src/main/protobuf/ws-duplications.proto @@ -52,4 +52,5 @@ message File { string subProjectUuid = 8; string subProjectName = 9; string branch = 10; + string pullRequest = 11; } diff --git a/sonar-ws/src/main/protobuf/ws-issues.proto b/sonar-ws/src/main/protobuf/ws-issues.proto index c869c9c1ff3..125f6e4c216 100644 --- a/sonar-ws/src/main/protobuf/ws-issues.proto +++ b/sonar-ws/src/main/protobuf/ws-issues.proto @@ -156,6 +156,7 @@ message Issue { optional string organization = 29; optional string branch = 30; + optional string pullRequest = 32; } message Transitions { @@ -234,6 +235,7 @@ message Component { optional int64 unusedProjectId = 9; optional int64 unusedSubProjectId = 10; optional string branch = 12; + optional string pullRequest = 13; } // Response of GET api/issues/changelog diff --git a/sonar-ws/src/main/protobuf/ws-measures.proto b/sonar-ws/src/main/protobuf/ws-measures.proto index 36cd4ab6174..c861426c3ea 100644 --- a/sonar-ws/src/main/protobuf/ws-measures.proto +++ b/sonar-ws/src/main/protobuf/ws-measures.proto @@ -76,6 +76,7 @@ message Component { optional string language = 10; repeated Measure measures = 11; optional string branch = 12; + optional string pullRequest = 13; } message Period { diff --git a/sonar-ws/src/main/protobuf/ws-projectbranches.proto b/sonar-ws/src/main/protobuf/ws-projectbranches.proto index 55d8e4032ce..e75439ab06e 100644 --- a/sonar-ws/src/main/protobuf/ws-projectbranches.proto +++ b/sonar-ws/src/main/protobuf/ws-projectbranches.proto @@ -45,16 +45,15 @@ message Branch { optional Status status = 5; optional bool isOrphan = 6; optional string analysisDate = 7; +} - message Status { - // Quality gate status is only present for long living branch - optional string qualityGateStatus = 1; - // Merge bugs, vulnerabilities and codeSmell are only present for short living branch - optional int64 bugs = 2; - optional int64 vulnerabilities = 3; - optional int64 codeSmells = 4; - } - +message Status { + // Quality gate status is only present for long living branch + optional string qualityGateStatus = 1; + // Merge bugs, vulnerabilities and codeSmell are only present for short living branch + optional int64 bugs = 2; + optional int64 vulnerabilities = 3; + optional int64 codeSmells = 4; } diff --git a/sonar-ws/src/main/protobuf/ws-projectpullrequests.proto b/sonar-ws/src/main/protobuf/ws-projectpullrequests.proto new file mode 100644 index 00000000000..e6bc0300953 --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-projectpullrequests.proto @@ -0,0 +1,49 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2016 SonarSource +// mailto:contact AT sonarsource DOT com +// +// SonarQube is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// SonarQube is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +syntax = "proto2"; + +package sonarqube.ws.projectpullrequest; + +option java_package = "org.sonarqube.ws"; +option java_outer_classname = "ProjectPullRequests"; +option optimize_for = SPEED; + +import "ws-commons.proto"; + +// WS api/project_pull_requests/list +message ListWsResponse { + repeated PullRequest pullRequests = 1; +} + +message PullRequest { + optional string key = 1; + optional string title = 2; + optional string branch = 3; + optional string base = 4; + optional Status status = 5; + optional bool isOrphan = 6; + optional string analysisDate = 7; + optional string url = 8; +} + +message Status { + optional int64 bugs = 2; + optional int64 vulnerabilities = 3; + optional int64 codeSmells = 4; +} diff --git a/sonar-ws/src/main/protobuf/ws-tests.proto b/sonar-ws/src/main/protobuf/ws-tests.proto index 74865970ca6..6d389e735bd 100644 --- a/sonar-ws/src/main/protobuf/ws-tests.proto +++ b/sonar-ws/src/main/protobuf/ws-tests.proto @@ -42,6 +42,7 @@ message CoveredFilesResponse { optional string longName = 3; optional int32 coveredLines = 4; optional string branch = 5; + optional string pullRequest = 6; } } @@ -58,6 +59,7 @@ message Test { optional string message = 9; optional string stacktrace = 10; optional string fileBranch = 11; + optional string filePullRequest = 12; } enum TestStatus { diff --git a/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java b/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java index 258ef9b92f3..7fbf43db21e 100644 --- a/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java +++ b/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java @@ -185,7 +185,7 @@ public class AutoAssignTest extends AbstractIssueTest { .setName(name) .setEmail(email) .setPassword("xxxxxxx") - .setScmAccounts(asList(scmAccounts))); + .setScmAccount(asList(scmAccounts))); } private static void deleteAllUsers() { diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDatePluginChangedTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDatePluginChangedTest.java index cff4e0f1aa5..76ae03a0a8b 100644 --- a/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDatePluginChangedTest.java +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDatePluginChangedTest.java @@ -95,7 +95,7 @@ public class IssueCreationDatePluginChangedTest { // Create a user and register her to receive notification on NewIssues tester.users().generate(t -> t.setLogin(USER_LOGIN).setPassword(USER_PASSWORD).setEmail(USER_EMAIL) - .setScmAccounts(ImmutableList.of("jhenry"))); + .setScmAccount(ImmutableList.of("jhenry"))); // Add notifications to the test user WsClient wsClient = newUserWsClient(ORCHESTRATOR, USER_LOGIN, USER_PASSWORD); wsClient.wsConnector().call(new PostRequest("api/notifications/add") diff --git a/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java index 3f56905e7b2..51e258bdfb8 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java @@ -241,7 +241,7 @@ public class LocalAuthenticationTest { .setLogin("test") .setName("Test") .setEmail("test@email.com") - .setScmAccounts(asList("test1", "test2")) + .setScmAccount(asList("test1", "test2")) .setPassword("password")); assertThat(checkAuthenticationWithAuthenticateWebService("test", "password")).isTrue(); diff --git a/travis.sh b/travis.sh index eaf9879c83c..5386944b148 100755 --- a/travis.sh +++ b/travis.sh @@ -34,7 +34,7 @@ function installNode { # function configureTravis { mkdir -p ~/.local - curl -sSL https://github.com/SonarSource/travis-utils/tarball/v41 | tar zx --strip-components 1 -C ~/.local + curl -sSL https://github.com/SonarSource/travis-utils/tarball/v47 | tar zx --strip-components 1 -C ~/.local source ~/.local/bin/install } configureTravis |