Quellcode durchsuchen

MMF-1134 Make pull request a 1st class citizen

SONAR-10366 Add pull request object to api/project_branches/list and api/ce/activity

SONAR-10365 Analyze pull requests as 1st class citizen

SONAR-10366 SONAR-10367 Create WS api/projet_pull_requests/list and delete

SONAR-10383 Add Pull Request information when listing CE tasks

SONAR-10365 Add key type in PROJECT_BRANCHES (#3063)

SONAR-10371 Add pullRequest parameter in the Web API (#3076)

* ComponentFinder searches by branch or pull request
* Add pullRequest parameter to WS api/issues/* WS
* Add pullRequest parameter to api/settings/* WS
* Add pullRequest parameter to api/badges/* WS
* Add pullRequest parameter to api/components/* WS
* Add pullRequest parameter to api/sources/* WS

SONAR-10368 Copy issue states from pull request after it's merged

SONAR-10373 Send notifications for events on issues of a pull request

SONAR-10365 Add pull_request_binary column in project_branches (#3073)

SONAR-10433 Store pull request in projects table

SONAR-10371 Add pullRequest field in the Web API

SONAR-10365 Analyze pull requests as 1st class citizen

BRANCH-45 Expose issue resolution for PR decoration

BRANCH-49 Basic support of pull request analysis on pull request branch

BRANCH-47 Fail when user tries to analyze a pull request and the plugin is not available

SONAR-10366 update pull request decorated links to project and issues

SONAR-10365 Use pull request id as key instead of branch name

SONAR-10454 Update embedded Git 1.4 and SVN 1.7

SONAR-10365 rename sonar.pullrequest.id to sonar.pullrequest.key

SONAR-10383 api/navigation/component returns the pull request key
tags/7.5
Teryk Bellahsene vor 6 Jahren
Ursprung
Commit
751e4000e4
100 geänderte Dateien mit 2326 neuen und 260 gelöschten Zeilen
  1. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  2. 4
    2
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  3. 1
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java
  4. 20
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
  5. 57
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java
  6. 1
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
  7. 6
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java
  8. 12
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
  9. 21
    19
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
  10. 7
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java
  11. 1
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
  12. 26
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java
  13. 38
    0
      server/sonar-db-dao/src/main/protobuf/db-project-branches.proto
  14. 10
    2
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
  15. 2
    2
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
  16. 204
    7
      server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java
  17. 32
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java
  18. 11
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java
  19. 56
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java
  20. 12
    2
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java
  21. 46
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java
  22. 46
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranches.java
  23. 6
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java
  24. 45
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequest.java
  25. 46
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranches.java
  26. 74
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranches.java
  27. 53
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranches.java
  28. 56
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest.java
  29. 57
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest.java
  30. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java
  31. 67
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest.java
  32. 61
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest.java
  33. 87
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest.java
  34. 108
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest.java
  35. 11
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest/project_branches.sql
  36. 12
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest/project_branches.sql
  37. 11
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest/project_branches.sql
  38. 12
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest/project_branches.sql
  39. 12
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest/project_branches.sql
  40. 12
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest/project_branches.sql
  41. 7
    1
      server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java
  42. 8
    2
      server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java
  43. 8
    1
      server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java
  44. 10
    1
      server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java
  45. 3
    1
      server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java
  46. 11
    0
      server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java
  47. 93
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java
  48. 142
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/ListAction.java
  49. 26
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsAction.java
  50. 32
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsModule.java
  51. 61
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWs.java
  52. 31
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWsParameters.java
  53. 23
    0
      server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/package-info.java
  54. 2
    4
      server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java
  55. 18
    13
      server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java
  56. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java
  57. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java
  58. 25
    11
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java
  59. 19
    3
      server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java
  60. 19
    5
      server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
  61. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java
  62. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java
  63. 34
    11
      server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java
  64. 33
    27
      server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java
  65. 14
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java
  66. 23
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java
  67. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java
  68. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java
  69. 3
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
  70. 19
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java
  71. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java
  72. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java
  73. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java
  74. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java
  75. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java
  76. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java
  77. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java
  78. 13
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java
  79. 12
    5
      server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java
  80. 26
    12
      server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java
  81. 8
    8
      server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java
  82. 24
    17
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java
  83. 11
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java
  84. 9
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java
  85. 13
    5
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java
  86. 12
    2
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
  87. 5
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java
  88. 5
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
  89. 9
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  90. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
  91. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java
  92. 39
    15
      server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
  93. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java
  94. 31
    19
      server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
  95. 11
    0
      server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java
  96. 40
    21
      server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java
  97. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  98. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java
  99. 13
    6
      server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java
  100. 0
    0
      server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java

+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java Datei anzeigen

@@ -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(

+ 4
- 2
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl Datei anzeigen

@@ -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,

+ 1
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java Datei anzeigen

@@ -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;

+ 20
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java Datei anzeigen

@@ -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) {

+ 57
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java Datei anzeigen

@@ -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,10 +54,18 @@ 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('}');

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java Datei anzeigen

@@ -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);


+ 6
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java Datei anzeigen

@@ -29,5 +29,10 @@ public enum BranchType {
/**
* Short-lived branch
*/
SHORT
SHORT,

/**
* Pull request
*/
PULL_REQUEST
}

+ 12
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java Datei anzeigen

@@ -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) {

+ 21
- 19
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java Datei anzeigen

@@ -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);
}
}

+ 7
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java Datei anzeigen

@@ -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) {

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java Datei anzeigen

@@ -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);

+ 26
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java Datei anzeigen

@@ -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
}

+ 38
- 0
server/sonar-db-dao/src/main/protobuf/db-project-branches.proto Datei anzeigen

@@ -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;
}

+ 10
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml Datei anzeigen

@@ -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">

+ 2
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml Datei anzeigen

@@ -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>

+ 204
- 7
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java Datei anzeigen

@@ -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

+ 32
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java Datei anzeigen

@@ -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();
}
}

+ 11
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java Datei anzeigen

@@ -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();
}
}

+ 56
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java Datei anzeigen

@@ -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;
@@ -105,6 +106,33 @@ public class ComponentKeyUpdaterDaoTest {
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
}

@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();
@@ -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());
}

+ 12
- 2
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java Datei anzeigen

@@ -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())

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java Datei anzeigen

@@ -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());
}
}

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranches.java Datei anzeigen

@@ -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());
}
}

+ 6
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java Datei anzeigen

@@ -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)
;
}
}

+ 45
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequest.java Datei anzeigen

@@ -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());
}
}

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranches.java Datei anzeigen

@@ -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());
}
}

+ 74
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranches.java Datei anzeigen

@@ -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()
);
}
}

+ 53
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranches.java Datei anzeigen

@@ -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;
});
}
}

+ 56
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest.java Datei anzeigen

@@ -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();
}
}

+ 57
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest.java Datei anzeigen

@@ -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();
}
}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java Datei anzeigen

@@ -36,7 +36,7 @@ public class DbVersion71Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 16);
verifyMigrationCount(underTest, 22);
}

}

+ 67
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest.java Datei anzeigen

@@ -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");
}
}

+ 61
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest.java Datei anzeigen

@@ -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");
}
}

+ 87
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest.java Datei anzeigen

@@ -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);
}
}

+ 108
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest.java Datei anzeigen

@@ -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);
}
}

+ 11
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranchesTest/project_branches.sql Datei anzeigen

@@ -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");

+ 12
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/AddPullRequestBinaryInProjectBranchesTest/project_branches.sql Datei anzeigen

@@ -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");

+ 11
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/IncreaseBranchTypeSizeForPullRequestTest/project_branches.sql Datei anzeigen

@@ -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");

+ 12
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MakeKeyTypeNotNullableInProjectBranchesTest/project_branches.sql Datei anzeigen

@@ -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");

+ 12
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/ReplaceIndexInProjectBranchesTest/project_branches.sql Datei anzeigen

@@ -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");

+ 12
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/SetKeyTypeToBranchInProjectBranchesTest/project_branches.sql Datei anzeigen

@@ -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");

+ 7
- 1
server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectConfigurationFactory.java Datei anzeigen

@@ -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);
}


+ 8
- 2
server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java Datei anzeigen

@@ -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);

+ 8
- 1
server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java Datei anzeigen

@@ -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);

+ 10
- 1
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java Datei anzeigen

@@ -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);

+ 3
- 1
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java Datei anzeigen

@@ -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())) {

+ 11
- 0
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java Datei anzeigen

@@ -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();
}

+ 93
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java Datei anzeigen

@@ -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);
}

}

+ 142
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/ListAction.java Datei anzeigen

@@ -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);
}
}

+ 26
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsAction.java Datei anzeigen

@@ -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
}

+ 32
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestWsModule.java Datei anzeigen

@@ -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);
}
}

+ 61
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWs.java Datei anzeigen

@@ -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);
}

}

+ 31
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/PullRequestsWsParameters.java Datei anzeigen

@@ -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
}
}

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/package-info.java Datei anzeigen

@@ -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;

+ 2
- 4
server/sonar-server/src/main/java/org/sonar/server/branch/ws/DeleteAction.java Datei anzeigen

@@ -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()) {

+ 18
- 13
server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java Datei anzeigen

@@ -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);
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java Datei anzeigen

@@ -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);


+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java Datei anzeigen

@@ -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)

+ 25
- 11
server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java Datei anzeigen

@@ -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();
}
}

/**

+ 19
- 3
server/sonar-server/src/main/java/org/sonar/server/component/ComponentFinder.java Datei anzeigen

@@ -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"),

+ 19
- 5
server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java Datei anzeigen

@@ -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);
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java Datei anzeigen

@@ -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);

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java Datei anzeigen

@@ -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";

+ 34
- 11
server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java Datei anzeigen

@@ -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;
}
}
}

+ 33
- 27
server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java Datei anzeigen

@@ -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;
@@ -402,6 +398,16 @@ public class TreeAction implements ComponentsWsAction {
return this;
}

@CheckForNull
public String getPullRequest() {
return pullRequest;
}

public Request setPullRequest(@Nullable String pullRequest) {
this.pullRequest = pullRequest;
return this;
}

@CheckForNull
private String getStrategy() {
return strategy;

+ 14
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java Datei anzeigen

@@ -82,6 +82,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
*/
@@ -92,6 +99,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();

}

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java Datei anzeigen

@@ -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<>();
@@ -148,6 +149,19 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder
return branch.getProperty();
}

@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");
@@ -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;
}

}

+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java Datei anzeigen

@@ -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();
}

+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/MutableAnalysisMetadataHolder.java Datei anzeigen

@@ -60,6 +60,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
*/

+ 3
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java Datei anzeigen

@@ -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;
}

+ 19
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java Datei anzeigen

@@ -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;
}


+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java Datei anzeigen

@@ -85,6 +85,11 @@ public class DefaultBranchImpl implements Branch {
return !isLegacyBranch;
}

@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();

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java Datei anzeigen

@@ -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);
}
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java Datei anzeigen

@@ -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());
}
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueTrackingDelegator.java Datei anzeigen

@@ -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);

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java Datei anzeigen

@@ -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();

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java Datei anzeigen

@@ -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;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java Datei anzeigen

@@ -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(

+ 13
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java Datei anzeigen

@@ -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;
}

}

+ 12
- 5
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsParser.java Datei anzeigen

@@ -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);
}

+ 26
- 12
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java Datei anzeigen

@@ -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

+ 8
- 8
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java Datei anzeigen

@@ -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));
}
}
}

+ 24
- 17
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java Datei anzeigen

@@ -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())));
}
}

+ 11
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/SearchRequest.java Datei anzeigen

@@ -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;
}
}

+ 9
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java Datei anzeigen

@@ -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: ")

+ 13
- 5
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java Datei anzeigen

@@ -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;
}

+ 12
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java Datei anzeigen

@@ -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);

+ 5
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java Datei anzeigen

@@ -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: ")

+ 5
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java Datei anzeigen

@@ -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;
}


+ 9
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java Datei anzeigen

@@ -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))

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java Datei anzeigen

@@ -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.

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java Datei anzeigen

@@ -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;
}


+ 39
- 15
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java Datei anzeigen

@@ -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;
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java Datei anzeigen

@@ -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);

+ 31
- 19
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java Datei anzeigen

@@ -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) {

+ 11
- 0
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeRequest.java Datei anzeigen

@@ -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;
@@ -81,6 +82,16 @@ class ComponentTreeRequest {
return this;
}

@CheckForNull
public String getPullRequest() {
return pullRequest;
}

public ComponentTreeRequest setPullRequest(@Nullable String pullRequest) {
this.pullRequest = pullRequest;
return this;
}

@CheckForNull
public String getStrategy() {
return strategy;

+ 40
- 21
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java Datei anzeigen

@@ -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;

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java Datei anzeigen

@@ -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,

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java Datei anzeigen

@@ -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

+ 13
- 6
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java Datei anzeigen

@@ -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);
}

}

+ 0
- 0
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchRequest.java Datei anzeigen


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

Laden…
Abbrechen
Speichern