ソースを参照

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 6年前
コミット
751e4000e4
100個のファイルの変更2326行の追加260行の削除
  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 ファイルの表示

+ 26 // level 1 + 26 // level 1
+ 53 // content of DaoModule + 53 // content of DaoModule
+ 3 // content of EsModule + 3 // content of EsModule
+ 57 // content of CorePropertyDefinitions
+ 59 // content of CorePropertyDefinitions
+ 1 // StopFlagContainer + 1 // StopFlagContainer
); );
assertThat( assertThat(

+ 4
- 2
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl ファイルの表示

"UUID" VARCHAR(50) NOT NULL PRIMARY KEY, "UUID" VARCHAR(50) NOT NULL PRIMARY KEY,
"PROJECT_UUID" VARCHAR(50) NOT NULL, "PROJECT_UUID" VARCHAR(50) NOT NULL,
"KEE" VARCHAR(255) 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), "MERGE_BRANCH_UUID" VARCHAR(50),
"PULL_REQUEST_BINARY" BLOB,
"CREATED_AT" BIGINT NOT NULL, "CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL "UPDATED_AT" BIGINT NOT NULL
); );
CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID"); 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" ( CREATE TABLE "ANALYSIS_PROPERTIES" (
"UUID" VARCHAR(40) NOT NULL PRIMARY KEY, "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,

+ 1
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java ファイルの表示



public static final String BRANCH_KEY = "branch"; public static final String BRANCH_KEY = "branch";
public static final String BRANCH_TYPE_KEY = "branchType"; public static final String BRANCH_TYPE_KEY = "branchType";
public static final String PULL_REQUEST = "pullRequest";


private String uuid; private String uuid;
private String taskUuid; private String taskUuid;

+ 20
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java ファイルの表示

} }


public void insert(DbSession dbSession, BranchDto dto) { public void insert(DbSession dbSession, BranchDto dto) {
setKeyType(dto);
mapper(dbSession).insert(dto, system2.now()); mapper(dbSession).insert(dto, system2.now());
} }


public void upsert(DbSession dbSession, BranchDto dto) { public void upsert(DbSession dbSession, BranchDto dto) {
BranchMapper mapper = mapper(dbSession); BranchMapper mapper = mapper(dbSession);
long now = system2.now(); long now = system2.now();
setKeyType(dto);
if (mapper.update(dto, now) == 0) { if (mapper.update(dto, now) == 0) {
mapper.insert(dto, now); 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) { public int updateMainBranchName(DbSession dbSession, String projectUuid, String newBranchKey) {
long now = system2.now(); long now = system2.now();
return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, 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) { public Collection<BranchDto> selectByComponent(DbSession dbSession, ComponentDto component) {

+ 57
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDto.java ファイルの表示

*/ */
package org.sonar.db.component; package org.sonar.db.component;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.sonar.db.protobuf.DbProjectBranches;


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;


private String projectUuid; 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; private String kee;


/**
* Key type, as provided by {@link KeyType}.
* Not null.
*/
private KeyType keyType;

/** /**
* Branch type, as provided by {@link BranchType}. * Branch type, as provided by {@link BranchType}.
* Not null. * Not null.
@Nullable @Nullable
private String mergeBranchUuid; private String mergeBranchUuid;


/**
* Pull Request data, such as branch name, title, url, and provider specific attributes
*/
@Nullable
private byte[] pullRequestBinary;

public String getUuid() { public String getUuid() {
return uuid; return uuid;
} }
return this; return this;
} }


BranchDto setKeyType(@Nullable KeyType keyType) {
this.keyType = keyType;
return this;
}

public BranchType getBranchType() { public BranchType getBranchType() {
return branchType; return branchType;
} }
return this; 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {
sb.append("uuid='").append(uuid).append('\''); sb.append("uuid='").append(uuid).append('\'');
sb.append(", projectUuid='").append(projectUuid).append('\''); sb.append(", projectUuid='").append(projectUuid).append('\'');
sb.append(", kee='").append(kee).append('\''); sb.append(", kee='").append(kee).append('\'');
sb.append(", keyType=").append(keyType);
sb.append(", branchType=").append(branchType); sb.append(", branchType=").append(branchType);
sb.append(", mergeBranchUuid='").append(mergeBranchUuid).append('\''); sb.append(", mergeBranchUuid='").append(mergeBranchUuid).append('\'');
sb.append('}'); sb.append('}');

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java ファイルの表示



int updateMainBranchName(@Param("projectUuid") String projectUuid, @Param("newBranchName") String newBranchName, @Param("now") long now); 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); BranchDto selectByUuid(@Param("uuid") String uuid);



+ 6
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchType.java ファイルの表示

/** /**
* Short-lived branch * Short-lived branch
*/ */
SHORT
SHORT,

/**
* Pull request
*/
PULL_REQUEST
} }

+ 12
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java ファイルの表示

import static org.sonar.db.DatabaseUtils.executeLargeUpdates; import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER; import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
import static org.sonar.db.component.ComponentDto.generateBranchKey; import static org.sonar.db.component.ComponentDto.generateBranchKey;
import static org.sonar.db.component.ComponentDto.generatePullRequestKey;


public class ComponentDao implements Dao { public class ComponentDao implements Dao {


return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, branch)); 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) { public List<ComponentDto> selectComponentsHavingSameKeyOrderedById(DbSession session, String key) {
return mapper(session).selectComponentsHavingSameKeyOrderedById(key); return mapper(session).selectComponentsHavingSameKeyOrderedById(key);
} }
} }


public java.util.Optional<ComponentDto> selectByKeyAndBranch(DbSession session, String key, String branch) { 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) { public List<UuidWithProjectUuidDto> selectAllViewsAndSubViews(DbSession session) {

+ 21
- 19
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java ファイルの表示

import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import org.sonar.api.resources.Scopes; import org.sonar.api.resources.Scopes;
import org.sonar.db.WildcardPosition; import org.sonar.db.WildcardPosition;


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format; 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.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.component.ComponentValidator.checkComponentKey; import static org.sonar.db.component.ComponentValidator.checkComponentKey;
import static org.sonar.db.component.ComponentValidator.checkComponentName; import static org.sonar.db.component.ComponentValidator.checkComponentName;
* Separator used to generate the key of the branch * Separator used to generate the key of the branch
*/ */
public static final String BRANCH_KEY_SEPARATOR = ":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 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_SEPARATOR = ".";
public static final String UUID_PATH_OF_ROOT = UUID_PATH_SEPARATOR; public static final String UUID_PATH_OF_ROOT = UUID_PATH_SEPARATOR;
private String organizationUuid; private String organizationUuid;


/** /**
* Non-empty and unique functional key
* Non-empty and unique functional key. Do not rename, used by MyBatis.
*/ */
private String kee; private String kee;


return UUID_PATH_SPLITTER.splitToList(uuidPath); 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() { public String getDbKey() {
return kee; return kee;
} }
* The key to be displayed to user, doesn't contain information on branches * The key to be displayed to user, doesn't contain information on branches
*/ */
public String getKey() { 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; return split.size() == 2 ? split.get(0) : kee;
} }


return split.size() == 2 ? split.get(1) : null; 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() { public String scope() {
return scope; return scope;
} }
return format("%s%s%s", componentKey, BRANCH_KEY_SEPARATOR, branch); 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 ファイルの表示



private static String branchBaseKey(String key) { private static String branchBaseKey(String key) {
int index = key.lastIndexOf(ComponentDto.BRANCH_KEY_SEPARATOR); 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) { 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 ファイルの表示

ComponentDto selectByKey(String key); ComponentDto selectByKey(String key);


@CheckForNull @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 @CheckForNull
ComponentDto selectById(long id); ComponentDto selectById(long id);

+ 26
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/component/KeyType.java ファイルの表示

/*
* 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 ファイルの表示

/*
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 ファイルの表示

pb.uuid as uuid, pb.uuid as uuid,
pb.project_uuid as projectUuid, pb.project_uuid as projectUuid,
pb.kee as kee, pb.kee as kee,
pb.key_type as keyType,
pb.branch_type as branchType, pb.branch_type as branchType,
pb.merge_branch_uuid as mergeBranchUuid
pb.merge_branch_uuid as mergeBranchUuid,
pb.pull_request_binary as pullRequestBinary
</sql> </sql>


<insert id="insert" parameterType="map" useGeneratedKeys="false"> <insert id="insert" parameterType="map" useGeneratedKeys="false">
uuid, uuid,
project_uuid, project_uuid,
kee, kee,
key_type,
branch_type, branch_type,
merge_branch_uuid, merge_branch_uuid,
pull_request_binary,
created_at, created_at,
updated_at updated_at
) values ( ) values (
#{dto.uuid, jdbcType=VARCHAR}, #{dto.uuid, jdbcType=VARCHAR},
#{dto.projectUuid, jdbcType=VARCHAR}, #{dto.projectUuid, jdbcType=VARCHAR},
#{dto.kee, jdbcType=VARCHAR}, #{dto.kee, jdbcType=VARCHAR},
#{dto.keyType, jdbcType=VARCHAR},
#{dto.branchType, jdbcType=VARCHAR}, #{dto.branchType, jdbcType=VARCHAR},
#{dto.mergeBranchUuid, jdbcType=VARCHAR}, #{dto.mergeBranchUuid, jdbcType=VARCHAR},
#{dto.pullRequestBinary, jdbcType=BINARY},
#{now, jdbcType=BIGINT}, #{now, jdbcType=BIGINT},
#{now, jdbcType=BIGINT} #{now, jdbcType=BIGINT}
) )
update project_branches update project_branches
set set
merge_branch_uuid = #{dto.mergeBranchUuid, jdbcType=VARCHAR}, merge_branch_uuid = #{dto.mergeBranchUuid, jdbcType=VARCHAR},
pull_request_binary = #{dto.pullRequestBinary, jdbcType=BINARY},
updated_at = #{now, jdbcType=BIGINT} updated_at = #{now, jdbcType=BIGINT}
where where
uuid = #{dto.uuid, jdbcType=VARCHAR} uuid = #{dto.uuid, jdbcType=VARCHAR}
from project_branches pb from project_branches pb
where where
pb.project_uuid = #{projectUuid, jdbcType=VARCHAR} and 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>


<select id="selectByProjectUuid" parameterType="string" resultType="org.sonar.db.component.BranchDto"> <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 ファイルの表示

p.kee=#{key,jdbcType=VARCHAR} p.kee=#{key,jdbcType=VARCHAR}
</select> </select>


<select id="selectByKeyAndBranch" parameterType="String" resultType="Component">
<select id="selectByKeyAndBranchKey" parameterType="String" resultType="Component">
SELECT SELECT
<include refid="componentColumns"/> <include refid="componentColumns"/>
FROM projects p FROM projects p
ON p.uuid = i.component_uuid ON p.uuid = i.component_uuid
JOIN project_branches b JOIN project_branches b
ON i.project_uuid = b.uuid 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 b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR}
AND i.status != 'CLOSED' AND i.status != 'CLOSED'
</select> </select>

+ 204
- 7
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java ファイルの表示

import org.sonar.api.utils.internal.TestSystem2; import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.protobuf.DbProjectBranches;


import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;


private static final long NOW = 1_000L; 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\", " + 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 "; "from project_branches ";
private System2 system2 = new TestSystem2().setNow(NOW); private System2 system2 = new TestSystem2().setNow(NOW);


entry("branchType", "SHORT"), entry("branchType", "SHORT"),
entry("kee", "feature/foo"), entry("kee", "feature/foo"),
entry("mergeBranchUuid", null), entry("mergeBranchUuid", null),
entry("pullRequestBinary", null),
entry("createdAt", 1_000L), entry("createdAt", 1_000L),
entry("updatedAt", 1_000L)); entry("updatedAt", 1_000L));
} }
underTest.insert(dbSession, dto2); underTest.insert(dbSession, dto2);


underTest.updateMainBranchName(dbSession, "U1", "master"); 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.getMergeBranchUuid()).isNull();
assertThat(loaded.getProjectUuid()).isEqualTo("U1"); assertThat(loaded.getProjectUuid()).isEqualTo("U1");
assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG);
} }


@Test @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(); BranchDto dto = new BranchDto();
dto.setProjectUuid("U1"); dto.setProjectUuid("U1");
dto.setUuid("U2"); dto.setUuid("U2");
dto.setBranchType(BranchType.SHORT); dto.setBranchType(BranchType.SHORT);
underTest.upsert(dbSession, dto); 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.getMergeBranchUuid()).isEqualTo("U3");
assertThat(loaded.getProjectUuid()).isEqualTo("U1"); assertThat(loaded.getProjectUuid()).isEqualTo("U1");
assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG); assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG);
} }


@Test @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(); BranchDto mainBranch = new BranchDto();
mainBranch.setProjectUuid("U1"); mainBranch.setProjectUuid("U1");
mainBranch.setUuid("U1"); mainBranch.setUuid("U1");
underTest.insert(dbSession, featureBranch); underTest.insert(dbSession, featureBranch);


// select the feature branch // 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.getUuid()).isEqualTo(featureBranch.getUuid());
assertThat(loaded.getKey()).isEqualTo(featureBranch.getKey()); assertThat(loaded.getKey()).isEqualTo(featureBranch.getKey());
assertThat(loaded.getProjectUuid()).isEqualTo(featureBranch.getProjectUuid()); assertThat(loaded.getProjectUuid()).isEqualTo(featureBranch.getProjectUuid());
assertThat(loaded.getMergeBranchUuid()).isEqualTo(featureBranch.getMergeBranchUuid()); assertThat(loaded.getMergeBranchUuid()).isEqualTo(featureBranch.getMergeBranchUuid());


// select a branch on another project with same branch name // 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 @Test

+ 32
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDtoTest.java ファイルの表示

*/ */
package org.sonar.db.component; package org.sonar.db.component;


import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.protobuf.DbProjectBranches;


import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;


public class BranchDtoTest { public class BranchDtoTest {


@Rule
public ExpectedException expectedException = ExpectedException.none();

private BranchDto underTest = new BranchDto(); private BranchDto underTest = new BranchDto();


@Test @Test


assertThat(underTest.isMain()).isFalse(); 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 ファイルの表示

assertThat(underTest.getKey()).isEqualTo("my_key"); assertThat(underTest.getKey()).isEqualTo("my_key");
assertThat(underTest.getBranch()).isNull(); 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 ファイルの表示

import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; 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.ComponentKeyUpdaterDao.computeNewKey;
import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newFileDto;
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); .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 @Test
public void bulk_updateKey_updates_branches_too() { public void bulk_updateKey_updates_branches_too() {
ComponentDto project = db.components().insertMainBranch(); ComponentDto project = db.components().insertMainBranch();
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); .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) { private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) {
return componentDto.setDbKey(key + ":" + componentDto.getDbKey()); return componentDto.setDbKey(key + ":" + componentDto.getDbKey());
} }

+ 12
- 2
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java ファイルの表示

import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; 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; import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;


public class ComponentTesting { public class ComponentTesting {


private static String generateKey(String key, ComponentDto parentModuleOrProject) { private static String generateKey(String key, ComponentDto parentModuleOrProject) {
String branch = parentModuleOrProject.getBranch(); 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) { public static ComponentDto newModuleDto(ComponentDto subProjectOrProject) {
checkArgument(project.qualifier().equals(Qualifiers.PROJECT)); checkArgument(project.qualifier().equals(Qualifiers.PROJECT));
checkArgument(project.getMainBranchProjectUuid() == null); checkArgument(project.getMainBranchProjectUuid() == null);
String branchName = branchDto.getKey(); String branchName = branchDto.getKey();
String branchSeparator = branchDto.getBranchType() == PULL_REQUEST ? ":PULL_REQUEST:" : ":BRANCH:";
String uuid = branchDto.getUuid(); String uuid = branchDto.getUuid();
return new ComponentDto() return new ComponentDto()
.setUuid(uuid) .setUuid(uuid)
.setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR) .setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR)
.setRootUuid(uuid) .setRootUuid(uuid)
// name of the branch is not mandatory on the main branch // 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()) .setMainBranchProjectUuid(project.uuid())
.setName(project.name()) .setName(project.name())
.setLongName(project.longName()) .setLongName(project.longName())

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/AddKeyTypeInProjectBranches.java ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

.add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class) .add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class)
.add(2014, "Migrate webhooks from SETTINGS table to WEBHOOKS table", MigrateWebhooksToWebhooksTable.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(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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示



@Test @Test
public void verify_migration_count() { 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

import org.sonar.api.config.Settings; import org.sonar.api.config.Settings;
import org.sonar.api.config.internal.ConfigurationBridge; import org.sonar.api.config.internal.ConfigurationBridge;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch; import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.settings.ChildSettings; import org.sonar.server.settings.ChildSettings;


import static org.sonar.db.component.ComponentDto.generateBranchKey; import static org.sonar.db.component.ComponentDto.generateBranchKey;
import static org.sonar.db.component.ComponentDto.generatePullRequestKey;


@ComputeEngineSide @ComputeEngineSide
public class ProjectConfigurationFactory { public class ProjectConfigurationFactory {
public Configuration newProjectConfiguration(String projectKey, Branch branch) { public Configuration newProjectConfiguration(String projectKey, Branch branch) {
Settings projectSettings = new ChildSettings(globalSettings); Settings projectSettings = new ChildSettings(globalSettings);
addSettings(projectSettings, projectKey); 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); return new ConfigurationBridge(projectSettings);
} }



+ 8
- 2
server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java ファイルの表示

import static org.sonar.api.measures.CoreMetrics.SQALE_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.TECHNICAL_DEBT_KEY;
import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_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.Level;
import static org.sonar.api.measures.Metric.ValueType; import static org.sonar.api.measures.Metric.ValueType;
import static org.sonar.api.measures.Metric.Level.ERROR; import static org.sonar.api.measures.Metric.Level.ERROR;
import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.valueOf; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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; import static org.sonarqube.ws.MediaTypes.SVG;


public class MeasureAction implements ProjectBadgesWsAction { public class MeasureAction implements ProjectBadgesWsAction {


private static final String PARAM_PROJECT = "project"; private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch"; 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 String PARAM_METRIC = "metric";


private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder() private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder()
.createParam(PARAM_BRANCH) .createParam(PARAM_BRANCH)
.setDescription("Branch key") .setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .setExampleValue(KEY_BRANCH_EXAMPLE_001);
action
.createParam(PARAM_PULL_REQUEST)
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
action.createParam(PARAM_METRIC) action.createParam(PARAM_METRIC)
.setDescription("Metric key") .setDescription("Metric key")
.setRequired(true) .setRequired(true)
response.stream().setMediaType(SVG); response.stream().setMediaType(SVG);
String projectKey = request.mandatoryParam(PARAM_PROJECT); String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH); String branch = request.param(PARAM_BRANCH);
String pullRequest = request.param(PARAM_PULL_REQUEST);
String metricKey = request.mandatoryParam(PARAM_METRIC); String metricKey = request.mandatoryParam(PARAM_METRIC);
try (DbSession dbSession = dbClient.openSession(false)) { 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); userSession.checkComponentPermission(USER, project);
MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey); MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey);
checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", 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 ファイルの表示

import static org.sonar.api.web.UserRole.USER; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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; import static org.sonarqube.ws.MediaTypes.SVG;


public class QualityGateAction implements ProjectBadgesWsAction { public class QualityGateAction implements ProjectBadgesWsAction {


private static final String PARAM_PROJECT = "project"; private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch"; private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";


private final UserSession userSession; private final UserSession userSession;
private final DbClient dbClient; private final DbClient dbClient;
.createParam(PARAM_BRANCH) .createParam(PARAM_BRANCH)
.setDescription("Branch key") .setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .setExampleValue(KEY_BRANCH_EXAMPLE_001);
action
.createParam(PARAM_PULL_REQUEST)
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
} }


@Override @Override
response.stream().setMediaType(SVG); response.stream().setMediaType(SVG);
String projectKey = request.mandatoryParam(PARAM_PROJECT); String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH); String branch = request.param(PARAM_BRANCH);
String pullRequest = request.param(PARAM_PULL_REQUEST);
try (DbSession dbSession = dbClient.openSession(false)) { 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); userSession.checkComponentPermission(USER, project);
Level qualityGateStatus = getQualityGate(dbSession, project); Level qualityGateStatus = getQualityGate(dbSession, project);
write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8); write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8);

+ 10
- 1
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java ファイルの表示

import static org.sonar.core.util.Protobuf.setNullable; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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.sonar.server.ws.WsUtils.writeProtobuf;


public class ProjectAction implements BatchWsAction { public class ProjectAction implements BatchWsAction {
private static final String PARAM_PROFILE = "profile"; private static final String PARAM_PROFILE = "profile";
private static final String PARAM_ISSUES_MODE = "issues_mode"; private static final String PARAM_ISSUES_MODE = "issues_mode";
private static final String PARAM_BRANCH = "branch"; private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";


private final ProjectDataLoader projectDataLoader; private final ProjectDataLoader projectDataLoader;


.setSince("6.6") .setSince("6.6")
.setDescription("Branch key") .setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .setExampleValue(KEY_BRANCH_EXAMPLE_001);

action
.createParam(PARAM_PULL_REQUEST)
.setSince("7.1")
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
} }


@Override @Override
.setModuleKey(wsRequest.mandatoryParam(PARAM_KEY)) .setModuleKey(wsRequest.mandatoryParam(PARAM_KEY))
.setProfileName(wsRequest.param(PARAM_PROFILE)) .setProfileName(wsRequest.param(PARAM_PROFILE))
.setIssuesMode(wsRequest.mandatoryParamAsBoolean(PARAM_ISSUES_MODE)) .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); WsProjectResponse projectResponse = buildResponse(data);
writeProtobuf(projectResponse, wsRequest, wsResponse); writeProtobuf(projectResponse, wsRequest, wsResponse);

+ 3
- 1
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java ファイルの表示

ProjectRepositories data = new ProjectRepositories(); ProjectRepositories data = new ProjectRepositories();
String moduleKey = query.getModuleKey(); String moduleKey = query.getModuleKey();
String branch = query.getBranch(); String branch = query.getBranch();
String pullRequest = query.getPullRequest();
ComponentDto mainModule = componentFinder.getByKey(session, moduleKey); ComponentDto mainModule = componentFinder.getByKey(session, moduleKey);
checkRequest(isProjectOrModule(mainModule), "Key '%s' belongs to a component which is not a Project", moduleKey); checkRequest(isProjectOrModule(mainModule), "Key '%s' belongs to a component which is not a Project", moduleKey);
boolean hasScanPerm = userSession.hasComponentPermission(SCAN_EXECUTION, mainModule) || boolean hasScanPerm = userSession.hasComponentPermission(SCAN_EXECUTION, mainModule) ||
userSession.hasPermission(OrganizationPermission.SCAN, mainModule.getOrganizationUuid()); userSession.hasPermission(OrganizationPermission.SCAN, mainModule.getOrganizationUuid());
boolean hasBrowsePerm = userSession.hasComponentPermission(USER, mainModule); boolean hasBrowsePerm = userSession.hasComponentPermission(USER, mainModule);
checkPermission(query.isIssuesMode(), hasScanPerm, hasBrowsePerm); 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); ComponentDto project = getProject(branchOrMainModule, session);
if (!project.getKey().equals(branchOrMainModule.getKey())) { if (!project.getKey().equals(branchOrMainModule.getKey())) {

+ 11
- 0
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataQuery.java ファイルの表示

private String profileName; private String profileName;
private boolean issuesMode; private boolean issuesMode;
private String branch; private String branch;
private String pullRequest;


private ProjectDataQuery() { private ProjectDataQuery() {
// No direct call // No direct call
return this; return this;
} }


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

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

public static ProjectDataQuery create() { public static ProjectDataQuery create() {
return new ProjectDataQuery(); return new ProjectDataQuery();
} }

+ 93
- 0
server/sonar-server/src/main/java/org/sonar/server/branch/pr/ws/DeleteAction.java ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

/*
* 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 ファイルの表示

*/ */
package org.sonar.server.branch.ws; 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.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;


import static org.sonar.server.branch.ws.BranchesWs.addBranchParam; import static org.sonar.server.branch.ws.BranchesWs.addBranchParam;
import static org.sonar.server.branch.ws.BranchesWs.addProjectParam; 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.ACTION_DELETE;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_BRANCH; 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.branch.ws.ProjectBranchesParameters.PARAM_PROJECT;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;


public class DeleteAction implements BranchWsAction { public class DeleteAction implements BranchWsAction {
private final DbClient dbClient; private final DbClient dbClient;
.setSince("6.6") .setSince("6.6")
.setDescription("Delete a non-main branch of a project.<br/>" + .setDescription("Delete a non-main branch of a project.<br/>" +
"Requires 'Administer' rights on the specified project.") "Requires 'Administer' rights on the specified project.")
.setResponseExample(Resources.getResource(getClass(), "list-example.json"))
.setPost(true) .setPost(true)
.setHandler(this); .setHandler(this);


checkPermission(project); checkPermission(project);


BranchDto branch = checkFoundWithOptional( 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); "Branch '%s' not found for project '%s'", branchKey, projectKey);


if (branch.isMain()) { if (branch.isMain()) {

+ 18
- 13
server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java ファイルの表示



import com.google.common.io.Resources; import com.google.common.io.Resources;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.UserRole; import org.sonar.api.web.UserRole;
import org.sonar.core.util.Protobuf; import org.sonar.core.util.Protobuf;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchDto;
checkPermission(project); checkPermission(project);
checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key"); 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() Map<String, BranchDto> mergeBranchesByUuid = dbClient.branchDao()
.selectByUuids(dbSession, branches.stream().map(BranchDto::getMergeBranchUuid).filter(Objects::nonNull).collect(toList())) .selectByUuids(dbSession, branches.stream().map(BranchDto::getMergeBranchUuid).filter(Objects::nonNull).collect(toList()))
.stream().collect(uniqueIndex(BranchDto::getUuid)); .stream().collect(uniqueIndex(BranchDto::getUuid));
Map<String, LiveMeasureDto> qualityGateMeasuresByComponentUuids = dbClient.liveMeasureDao() 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() Map<String, BranchStatistics> branchStatisticsByBranchUuid = issueIndex.searchBranchStatistics(project.uuid(), branches.stream()
.filter(b -> b.getBranchType().equals(SHORT)) .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() 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(); ProjectBranches.ListWsResponse.Builder protobufResponse = ProjectBranches.ListWsResponse.newBuilder();
branches.forEach(b -> addBranch(protobufResponse, b, mergeBranchesByUuid, qualityGateMeasuresByComponentUuids.get(b.getUuid()), branchStatisticsByBranchUuid.get(b.getUuid()), 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); WsUtils.writeProtobuf(protobufResponse.build(), request, response);
} }
} }


private static void addBranch(ProjectBranches.ListWsResponse.Builder response, BranchDto branch, Map<String, BranchDto> mergeBranchesByUuid, 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()))); ProjectBranches.Branch.Builder builder = toBranchBuilder(branch, Optional.ofNullable(mergeBranchesByUuid.get(branch.getMergeBranchUuid())));
setBranchStatus(builder, branch, qualityGateMeasure, branchStatistics); setBranchStatus(builder, branch, qualityGateMeasure, branchStatistics);
if (analysisDate != null) { if (analysisDate != null) {
setNullable(branchKey, builder::setName); setNullable(branchKey, builder::setName);
builder.setIsMain(branch.isMain()); builder.setIsMain(branch.isMain());
builder.setType(Common.BranchType.valueOf(branch.getBranchType().name())); builder.setType(Common.BranchType.valueOf(branch.getBranchType().name()));
if (branch.getBranchType().equals(SHORT)) {
if (branch.getBranchType() == SHORT) {
if (mergeBranch.isPresent()) { if (mergeBranch.isPresent()) {
String mergeBranchKey = mergeBranch.get().getKey(); String mergeBranchKey = mergeBranch.get().getKey();
builder.setMergeBranch(mergeBranchKey); builder.setMergeBranch(mergeBranchKey);
} }


private static void setBranchStatus(ProjectBranches.Branch.Builder builder, BranchDto branch, @Nullable LiveMeasureDto qualityGateMeasure, 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) { if (branch.getBranchType() == LONG && qualityGateMeasure != null) {
Protobuf.setNullable(qualityGateMeasure.getDataAsString(), statusBuilder::setQualityGateStatus); Protobuf.setNullable(qualityGateMeasure.getDataAsString(), statusBuilder::setQualityGateStatus);
} }

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/branch/ws/RenameAction.java ファイルの表示

ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey); ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey);
checkPermission(project); 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(), checkArgument(!existingBranch.filter(b -> !b.isMain()).isPresent(),
"Impossible to update branch name: a branch with name \"%s\" already exists in the project.", newBranchName); "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 ファイルの表示

.setChangelog( .setChangelog(
new Change("5.5", "it's no more possible to specify the page parameter."), 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.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"); .setSince("5.2");


action.createParam(PARAM_COMPONENT_ID) action.createParam(PARAM_COMPONENT_ID)

+ 25
- 11
server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java ファイルの表示

builder.setSubmittedAt(formatDateTime(new Date(dto.getCreatedAt()))); builder.setSubmittedAt(formatDateTime(new Date(dto.getCreatedAt())));
setNullable(dto.getStartedAt(), builder::setStartedAt, DateUtils::formatDateTime); setNullable(dto.getStartedAt(), builder::setStartedAt, DateUtils::formatDateTime);
setNullable(computeExecutionTimeMs(dto), builder::setExecutionTimeMs); setNullable(computeExecutionTimeMs(dto), builder::setExecutionTimeMs);
setBranch(builder, dto.getUuid(), componentDtoCache);
setBranchOrPullRequest(builder, dto.getUuid(), componentDtoCache);
return builder.build(); return builder.build();
} }


builder.setLogs(false); builder.setLogs(false);
setNullable(dto.getComponentUuid(), uuid -> setComponent(builder, uuid, componentDtoCache).setComponentId(uuid)); setNullable(dto.getComponentUuid(), uuid -> setComponent(builder, uuid, componentDtoCache).setComponentId(uuid));
String analysisUuid = dto.getAnalysisUuid(); 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(analysisUuid, builder::setAnalysisId);
setNullable(dto.getSubmitterLogin(), builder::setSubmitterLogin); setNullable(dto.getSubmitterLogin(), builder::setSubmitterLogin);
builder.setSubmittedAt(formatDateTime(new Date(dto.getSubmittedAt()))); builder.setSubmittedAt(formatDateTime(new Date(dto.getSubmittedAt())));
return builder; 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 -> { 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; return builder;
} }


return organizationDto.getKey(); return organizationDto.getKey();
} }


Optional<String> getBranchName(String taskUuid) {
Optional<String> getBranchKey(String taskUuid) {
return characteristicsByTaskUuid.get(taskUuid).stream() return characteristicsByTaskUuid.get(taskUuid).stream()
.filter(c -> c.getKey().equals(CeTaskCharacteristicDto.BRANCH_KEY)) .filter(c -> c.getKey().equals(CeTaskCharacteristicDto.BRANCH_KEY))
.map(CeTaskCharacteristicDto::getValue) .map(CeTaskCharacteristicDto::getValue)
.map(c -> Common.BranchType.valueOf(c.getValue())) .map(c -> Common.BranchType.valueOf(c.getValue()))
.findAny(); .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 ファイルの表示

throw new NotFoundException(format("Component '%s' on branch '%s' not found", key, branch)); 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 { public enum ParamNames {
UUID_AND_KEY("uuid", "key"), UUID_AND_KEY("uuid", "key"),
ID_AND_KEY("id", "key"), ID_AND_KEY("id", "key"),
COMPONENT_ID_AND_KEY("componentId", "componentKey"), 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"), DEVELOPER_ID_AND_KEY("developerId", "developerKey"),
COMPONENT_ID_AND_COMPONENT("componentId", "component"), COMPONENT_ID_AND_COMPONENT("componentId", "component"),
PROJECT_ID_AND_PROJECT("projectId", "project"), PROJECT_ID_AND_PROJECT("projectId", "project"),

+ 19
- 5
server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java ファイルの表示

import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; 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.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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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 { public class AppAction implements ComponentsWsAction {


.setSince("6.6") .setSince("6.6")
.setInternal(true) .setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .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 @Override


private ComponentDto loadComponent(DbSession dbSession, Request request) { private ComponentDto loadComponent(DbSession dbSession, Request request) {
String componentUuid = request.param(PARAM_COMPONENT_ID); 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.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) { private void appendComponent(JsonWriter json, ComponentDto component, UserSession userSession, DbSession session) {
if (branch != null) { if (branch != null) {
json.prop("branch", branch); json.prop("branch", branch);
} }
String pullRequest = project.getPullRequest();
if (pullRequest != null) {
json.prop("pullRequest", pullRequest);
}


json.prop("fav", isFavourite); json.prop("fav", isFavourite);
} }

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java ファイルの表示

.setName(dto.name()) .setName(dto.name())
.setQualifier(dto.qualifier()); .setQualifier(dto.qualifier());
setNullable(emptyToNull(dto.getBranch()), wsComponent::setBranch); setNullable(emptyToNull(dto.getBranch()), wsComponent::setBranch);
setNullable(emptyToNull(dto.getPullRequest()), wsComponent::setPullRequest);
setNullable(emptyToNull(dto.path()), wsComponent::setPath); setNullable(emptyToNull(dto.path()), wsComponent::setPath);
setNullable(emptyToNull(dto.description()), wsComponent::setDescription); setNullable(emptyToNull(dto.description()), wsComponent::setDescription);
setNullable(emptyToNull(dto.language()), wsComponent::setLanguage); setNullable(emptyToNull(dto.language()), wsComponent::setLanguage);

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/component/ws/MeasuresWsParameters.java ファイルの表示

public static final String DEPRECATED_PARAM_BASE_COMPONENT_KEY = "baseComponentKey"; public static final String DEPRECATED_PARAM_BASE_COMPONENT_KEY = "baseComponentKey";
public static final String PARAM_COMPONENT = "component"; public static final String PARAM_COMPONENT = "component";
public static final String PARAM_BRANCH = "branch"; 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_STRATEGY = "strategy";
public static final String PARAM_QUALIFIERS = "qualifiers"; public static final String PARAM_QUALIFIERS = "qualifiers";
public static final String PARAM_METRICS = "metrics"; public static final String PARAM_METRICS = "metrics";

+ 34
- 11
server/sonar-server/src/main/java/org/sonar/server/component/ws/ShowAction.java ファイルの表示

import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.IntStream; 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.Change;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Components.ShowWsResponse; import org.sonarqube.ws.Components.ShowWsResponse;


import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; 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.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT;
import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW; 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;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; 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 { public class ShowAction implements ComponentsWsAction {
private final UserSession userSession; private final UserSession userSession;
.setExampleValue(KEY_BRANCH_EXAMPLE_001) .setExampleValue(KEY_BRANCH_EXAMPLE_001)
.setInternal(true) .setInternal(true)
.setSince("6.6"); .setSince("6.6");

action.createParam(PARAM_PULL_REQUEST)
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
.setInternal(true)
.setSince("7.1");
} }


@Override @Override
private ShowWsResponse doHandle(Request request) { private ShowWsResponse doHandle(Request request) {
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto component = loadComponent(dbSession, request); ComponentDto component = loadComponent(dbSession, request);
userSession.checkComponentPermission(UserRole.USER, component);
Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.projectUuid()); Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.projectUuid());
List<ComponentDto> ancestors = dbClient.componentDao().selectAncestors(dbSession, component); List<ComponentDto> ancestors = dbClient.componentDao().selectAncestors(dbSession, component);
OrganizationDto organizationDto = componentFinder.getOrganization(dbSession, component); OrganizationDto organizationDto = componentFinder.getOrganization(dbSession, component);
String componentId = request.getId(); String componentId = request.getId();
String componentKey = request.getKey(); String componentKey = request.getKey();
String branch = request.getBranch(); 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) { private static ShowWsResponse buildResponse(ComponentDto component, OrganizationDto organizationDto, List<ComponentDto> orderedAncestors, Optional<SnapshotDto> lastAnalysis) {
return new Request() return new Request()
.setId(request.param(PARAM_COMPONENT_ID)) .setId(request.param(PARAM_COMPONENT_ID))
.setKey(request.param(PARAM_COMPONENT)) .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 static class Request {
private String id; private String id;
private String key; private String key;
private String branch; private String branch;
private String pullRequest;


@CheckForNull @CheckForNull
public String getId() { public String getId() {
this.branch = branch; this.branch = branch;
return this; 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 ファイルの表示

import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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.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.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_TREE; 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;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID; 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_QUALIFIERS;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY; 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 { public class TreeAction implements ComponentsWsAction {


.setInternal(true) .setInternal(true)
.setSince("6.6"); .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) action.createSortParams(SORTS, NAME_SORT, true)
.setDescription("Comma-separated list of sort fields") .setDescription("Comma-separated list of sort fields")
.setExampleValue(NAME_SORT + ", " + PATH_SORT); .setExampleValue(NAME_SORT + ", " + PATH_SORT);


private ComponentDto loadComponent(DbSession dbSession, Request request) { private ComponentDto loadComponent(DbSession dbSession, Request request) {
String componentId = request.getBaseComponentId(); String componentId = request.getBaseComponentId();
String componentKey = request.getBaseComponentKey();
String componentKey = request.getComponent();
String branch = request.getBranch(); 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) { private Map<String, ComponentDto> searchReferenceComponentsByUuid(DbSession dbSession, List<ComponentDto> components) {
private static Request toTreeWsRequest(org.sonar.api.server.ws.Request request) { private static Request toTreeWsRequest(org.sonar.api.server.ws.Request request) {
return new Request() return new Request()
.setBaseComponentId(request.param(PARAM_COMPONENT_ID)) .setBaseComponentId(request.param(PARAM_COMPONENT_ID))
.setBaseComponentKey(request.param(PARAM_COMPONENT))
.setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH)) .setBranch(request.param(PARAM_BRANCH))
.setPullRequest(request.param(PARAM_PULL_REQUEST))
.setStrategy(request.mandatoryParam(PARAM_STRATEGY)) .setStrategy(request.mandatoryParam(PARAM_STRATEGY))
.setQuery(request.param(Param.TEXT_QUERY)) .setQuery(request.param(Param.TEXT_QUERY))
.setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS)) .setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS))


private static class Request { private static class Request {
private String baseComponentId; private String baseComponentId;
private String baseComponentKey;
private String component; private String component;
private String branch; private String branch;
private String pullRequest;
private String strategy; private String strategy;
private List<String> qualifiers; private List<String> qualifiers;
private String query; private String query;
return this; 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) { public Request setComponent(@Nullable String component) {
this.component = component; this.component = component;
return this; return this;
return this; return this;
} }


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

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

@CheckForNull @CheckForNull
private String getStrategy() { private String getStrategy() {
return strategy; return strategy;

+ 14
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolder.java ファイルの表示

*/ */
boolean isLongLivingBranch(); 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 * @throws IllegalStateException if cross project duplication flag has not been set
*/ */
*/ */
Branch getBranch(); 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 * The project as represented by the main branch. It is used to load settings
* like Quality gates, webhooks and configuration. * like Quality gates, webhooks and configuration.
* Plugins used during the analysis on scanner side * Plugins used during the analysis on scanner side
*/ */
Map<String, ScannerPlugin> getScannerPluginsByKey(); Map<String, ScannerPlugin> getScannerPluginsByKey();

} }

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/AnalysisMetadataHolderImpl.java ファイルの表示

private final InitializedProperty<Analysis> baseProjectSnapshot = new InitializedProperty<>(); private final InitializedProperty<Analysis> baseProjectSnapshot = new InitializedProperty<>();
private final InitializedProperty<Boolean> crossProjectDuplicationEnabled = new InitializedProperty<>(); private final InitializedProperty<Boolean> crossProjectDuplicationEnabled = new InitializedProperty<>();
private final InitializedProperty<Branch> branch = 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<Project> project = new InitializedProperty<>();
private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>();
private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>();
return branch.getProperty(); 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 @Override
public MutableAnalysisMetadataHolder setProject(Project project) { public MutableAnalysisMetadataHolder setProject(Project project) {
checkState(!this.project.isInitialized(), "Project has already been set"); checkState(!this.project.isInitialized(), "Project has already been set");
return pluginsByKey.getProperty(); return pluginsByKey.getProperty();
} }


@Override
public boolean isShortLivingBranch() { public boolean isShortLivingBranch() {
checkState(this.branch.isInitialized(), BRANCH_NOT_SET); checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
Branch prop = branch.getProperty(); Branch prop = branch.getProperty();
return prop != null && prop.getType() == BranchType.SHORT; return prop != null && prop.getType() == BranchType.SHORT;
} }


@Override
public boolean isLongLivingBranch() { public boolean isLongLivingBranch() {
checkState(this.branch.isInitialized(), BRANCH_NOT_SET); checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
Branch prop = branch.getProperty(); Branch prop = branch.getProperty();
return prop != null && prop.getType() == BranchType.LONG; 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 ファイルの表示

* or not. * or not.
*/ */
boolean supportsCrossProjectCpd(); 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 ファイルの表示

*/ */
MutableAnalysisMetadataHolder setBranch(Branch branch); 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 * @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 ファイルの表示

import static java.util.Optional.ofNullable; 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.FAILED;
import static org.sonar.api.ce.posttask.CeTask.Status.SUCCESS; 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). * Responsible for calling {@link PostProjectAnalysisTask} implementations (if any).
private BranchImpl createBranch() { private BranchImpl createBranch() {
org.sonar.server.computation.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch(); org.sonar.server.computation.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch();
if (!analysisBranch.isLegacyFeature()) { 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; return null;
} }

+ 19
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/BranchPersisterImpl.java ファイルの表示

import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto; 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.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch; import org.sonar.server.computation.task.projectanalysis.analysis.Branch;


if (branch.isMain()) { if (branch.isMain()) {
checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis"); checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis");
branchComponentDto = branchComponentDtoOpt.get(); branchComponentDto = branchComponentDtoOpt.get();

} else { } else {
// inserts new row in table projects if it's the first time branch is analyzed // inserts new row in table projects if it's the first time branch is analyzed
branchComponentDto = branchComponentDtoOpt.or(() -> insertIntoProjectsTable(dbSession, branchUuid)); branchComponentDto = branchComponentDtoOpt.or(() -> insertIntoProjectsTable(dbSession, branchUuid));

} }

// insert or update in table project_branches // insert or update in table project_branches
dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch)); dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch));
} }
return (first != null) ? first : second; return (first != null) ? first : second;
} }


private static BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
private BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
BranchDto dto = new BranchDto(); BranchDto dto = new BranchDto();
dto.setUuid(componentDto.uuid()); dto.setUuid(componentDto.uuid());

// MainBranchProjectUuid will be null if it's a main branch // MainBranchProjectUuid will be null if it's a main branch
dto.setProjectUuid(firstNonNull(componentDto.getMainBranchProjectUuid(), componentDto.projectUuid())); dto.setProjectUuid(firstNonNull(componentDto.getMainBranchProjectUuid(), componentDto.projectUuid()));
dto.setKey(branch.getName());
dto.setBranchType(branch.getType()); dto.setBranchType(branch.getType());

// merge branch is only present if it's a short living branch // merge branch is only present if it's a short living branch
dto.setMergeBranchUuid(branch.getMergeBranchUuid().orElse(null)); 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; return dto;
} }



+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/DefaultBranchImpl.java ファイルの表示

return !isLegacyBranch; return !isLegacyBranch;
} }


@Override
public String getPullRequestId() {
throw new IllegalStateException("Only a branch of type PULL_REQUEST can have a pull request id.");
}

@Override @Override
public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) { public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) {
String moduleWithBranch = module.getKey(); String moduleWithBranch = module.getKey();

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MergeBranchComponentUuids.java ファイルの表示

import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;


import static com.google.common.base.Preconditions.checkState; 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 * Cache a map between component keys and uuids in the merge branch
@CheckForNull @CheckForNull
public String getUuid(String dbKey) { public String getUuid(String dbKey) {
lazyInit(); lazyInit();
String cleanComponentKey = removeBranchFromKey(dbKey);
String cleanComponentKey = removeBranchAndPullRequestFromKey(dbKey);
return uuidsByKey.get(cleanComponentKey); return uuidsByKey.get(cleanComponentKey);
} }
} }

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssues.java ファイルの表示

import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.KeyWithUuidDto; 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. * Cache a map of component key -> uuid in short branches that have issues with status either RESOLVED or CONFIRMED.
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid); List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid);
for (KeyWithUuidDto dto : components) { 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 ファイルの表示

} }


public TrackingResult track(Component component) { public TrackingResult track(Component component) {
if (analysisMetadataHolder.isShortLivingBranch()) {
if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) {
return standardResult(shortBranchTracker.track(component)); return standardResult(shortBranchTracker.track(component));
} else if (isFirstAnalysisSecondaryLongLivingBranch()) { } else if (isFirstAnalysisSecondaryLongLivingBranch()) {
Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component); Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component);

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java ファイルの表示

} }


public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) { public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) {
String componentKey = ComponentDto.removeBranchFromKey(component.getKey());
String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getKey());
Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey); Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
if (uuids.isEmpty()) { if (uuids.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java ファイルの表示

} }


private Optional<QualityGate> getShortLivingBranchQualityGate() { private Optional<QualityGate> getShortLivingBranchQualityGate() {
if (analysisMetadataHolder.isShortLivingBranch()) {
if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) {
Optional<QualityGate> qualityGate = qualityGateService.findById(ShortLivingBranchQualityGate.ID); Optional<QualityGate> qualityGate = qualityGateService.findById(ShortLivingBranchQualityGate.ID);
if (qualityGate.isPresent()) { if (qualityGate.isPresent()) {
return qualityGate; return qualityGate;

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java ファイルの表示



@Override @Override
public void execute() { 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; return;
} }
new DepthTraversalTypeAwareCrawler( new DepthTraversalTypeAwareCrawler(

+ 13
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java ファイルの表示

import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.annotation.CheckForNull;
import org.sonar.api.issue.Issue; import org.sonar.api.issue.Issue;
import org.sonar.api.utils.Duration; import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.issue.notification.NewIssuesStatistics; import org.sonar.server.issue.notification.NewIssuesStatistics;
import org.sonar.server.notification.NotificationService; 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; import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;


/** /**
IssueChangeNotification changeNotification = new IssueChangeNotification(); IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName()); changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
changeNotification.setIssue(issue); 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())); getComponentKey(issue).ifPresent(c -> changeNotification.setComponent(c.getPublicKey(), c.getName()));
service.deliver(changeNotification); service.deliver(changeNotification);
} }
NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics(); NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics();
NewIssuesNotification notification = newIssuesNotificationFactory NewIssuesNotification notification = newIssuesNotificationFactory
.newNewIssuesNotication() .newNewIssuesNotication()
.setProject(project.getPublicKey(), project.getName(), getBranchName())
.setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest())
.setProjectVersion(project.getReportAttributes().getVersion()) .setProjectVersion(project.getReportAttributes().getVersion())
.setAnalysisDate(new Date(analysisDate)) .setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), globalStatistics) .setStatistics(project.getName(), globalStatistics)
.newMyNewIssuesNotification() .newMyNewIssuesNotification()
.setAssignee(assignee); .setAssignee(assignee);
myNewIssuesNotification myNewIssuesNotification
.setProject(project.getPublicKey(), project.getName(), getBranchName())
.setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest())
.setProjectVersion(project.getReportAttributes().getVersion()) .setProjectVersion(project.getReportAttributes().getVersion())
.setAnalysisDate(new Date(analysisDate)) .setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), assigneeStatistics) .setStatistics(project.getName(), assigneeStatistics)
return "Send issue notifications"; return "Send issue notifications";
} }


@CheckForNull
private String getBranchName() { private String getBranchName() {
Branch branch = analysisMetadataHolder.getBranch(); 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 ファイルの表示

this.componentDao = componentDao; 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(); Map<String, ComponentDto> componentsByKey = newHashMap();
List<Block> blocks = newArrayList(); List<Block> blocks = newArrayList();
if (duplicationsData != null) { if (duplicationsData != null) {
String size = bCursor.getAttrValue("l"); String size = bCursor.getAttrValue("l");
String componentKey = bCursor.getAttrValue("r"); String componentKey = bCursor.getAttrValue("r");
if (from != null && size != null && componentKey != null) { 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())); Collections.sort(duplications, new DuplicationComparator(component.uuid(), component.projectUuid()));
return blocks; 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); String componentKey = convertToKey(componentDbKey);
ComponentDto component = componentsByKey.get(componentKey); ComponentDto component = componentsByKey.get(componentKey);
if (component == null) { 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; component = componentDtoOptional.isPresent() ? componentDtoOptional.get() : null;
componentsByKey.put(componentKey, component); componentsByKey.put(componentKey, component);
} }

+ 26
- 12
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java ファイルの表示

import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY; 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_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.ws.WsUtils.writeProtobuf;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;


public class ShowAction implements DuplicationsWsAction { 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 DbClient dbClient;
private final DuplicationsParser parser; private final DuplicationsParser parser;
private final ShowResponseBuilder responseBuilder; private final ShowResponseBuilder responseBuilder;
new Change("6.5", "The fields 'uuid', 'projectUuid', 'subProjectUuid' are deprecated in the response.")); new Change("6.5", "The fields 'uuid', 'projectUuid', 'subProjectUuid' are deprecated in the response."));


action action
.createParam("key")
.createParam(PARAM_KEY)
.setDescription("File key") .setDescription("File key")
.setExampleValue("my_project:/src/foo/Bar.php"); .setExampleValue("my_project:/src/foo/Bar.php");


action action
.createParam("uuid")
.createParam(PARAM_UUID)
.setDeprecatedSince("6.5") .setDeprecatedSince("6.5")
.setDescription("File ID. If provided, 'key' must not be provided.") .setDescription("File ID. If provided, 'key' must not be provided.")
.setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2"); .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2");


action action
.createParam("branch")
.createParam(PARAM_BRANCH)
.setDescription("Branch key") .setDescription("Branch key")
.setInternal(true) .setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .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 @Override
userSession.checkComponentPermission(UserRole.CODEVIEWER, component); userSession.checkComponentPermission(UserRole.CODEVIEWER, component);
String duplications = findDataFromComponent(dbSession, component); String duplications = findDataFromComponent(dbSession, component);
String branch = component.getBranch(); 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) { 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 @CheckForNull

+ 8
- 8
server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowResponseBuilder.java ファイルの表示

this.componentDao = componentDao; 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(); ShowResponse.Builder response = ShowResponse.newBuilder();
Map<String, String> refByComponentKey = newHashMap(); Map<String, String> refByComponentKey = newHashMap();
blocks.stream() blocks.stream()
.map(block -> toWsDuplication(block, refByComponentKey)) .map(block -> toWsDuplication(block, refByComponentKey))
.forEach(response::addDuplications); .forEach(response::addDuplications);


writeFiles(session, response, refByComponentKey, branch);
writeFiles(session, response, refByComponentKey, branch, pullRequest);


return response.build(); return response.build();
} }
return block; 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(); Duplications.File.Builder wsFile = Duplications.File.newBuilder();
wsFile.setKey(file.getKey()); wsFile.setKey(file.getKey());
wsFile.setUuid(file.uuid()); wsFile.setUuid(file.uuid());
wsFile.setSubProjectUuid(subProject.uuid()); wsFile.setSubProjectUuid(subProject.uuid());
wsFile.setSubProjectName(subProject.longName()); wsFile.setSubProjectName(subProject.longName());
} }
if (branch != null) {
wsFile.setBranch(branch);
}
setNullable(branch, wsFile::setBranch);
setNullable(pullRequest, wsFile::setPullRequest);
return wsFile; return wsFile;
}); });
return wsFile.build(); 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> projectsByUuid = newHashMap();
Map<String, ComponentDto> parentModulesByUuid = newHashMap(); Map<String, ComponentDto> parentModulesByUuid = newHashMap();
Map<String, Duplications.File> filesByRef = response.getMutableFiles(); Map<String, Duplications.File> filesByRef = response.getMutableFiles();


ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session); ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session);
ComponentDto parentModule = getParentProject(file.getRootUuid(), parentModulesByUuid, 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 ファイルの表示

Collection<String> componentRootUuids = request.getComponentRootUuids(); Collection<String> componentRootUuids = request.getComponentRootUuids();
Collection<String> componentRoots = request.getComponentRoots(); Collection<String> componentRoots = request.getComponentRoots();
String branch = request.getBranch(); String branch = request.getBranch();
String pullRequest = request.getPullRequest();


boolean effectiveOnComponentOnly = false; boolean effectiveOnComponentOnly = false;


if (componentRootUuids != null) { if (componentRootUuids != null) {
allComponents.addAll(getComponentsFromUuids(session, componentRootUuids)); allComponents.addAll(getComponentsFromUuids(session, componentRootUuids));
} else if (componentRoots != null) { } else if (componentRoots != null) {
allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch));
allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch, pullRequest));
} else if (components != null) { } else if (components != null) {
allComponents.addAll(getComponentsFromKeys(session, components, branch));
allComponents.addAll(getComponentsFromKeys(session, components, branch, pullRequest));
effectiveOnComponentOnly = true; effectiveOnComponentOnly = true;
} else if (componentUuids != null) { } else if (componentUuids != null) {
allComponents.addAll(getComponentsFromUuids(session, componentUuids)); allComponents.addAll(getComponentsFromUuids(session, componentUuids));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly); effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
} else if (componentKeys != null) { } else if (componentKeys != null) {
allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch));
allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch, pullRequest));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly); effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
} }


.count() <= 1; .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); builder.onComponentOnly(onComponentOnly);
if (onComponentOnly) { if (onComponentOnly) {
builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList())); 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; return;
} }


if (projectUuids != null) { if (projectUuids != null) {
builder.projectUuids(projectUuids); builder.projectUuids(projectUuids);
} else if (projectKeys != null) { } 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())); 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.moduleUuids(request.getModuleUuids());
builder.directories(request.getDirectories()); builder.directories(request.getDirectories());
Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet()); 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)); 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(); String qualifier = qualifiers.iterator().next();
switch (qualifier) { switch (qualifier) {
case Qualifiers.VIEW: case Qualifiers.VIEW:
builder.directories(directoryPaths); 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()) { if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
return singletonList(UNKNOWN_COMPONENT); return singletonList(UNKNOWN_COMPONENT);
} }
return mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid; 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 ファイルの表示

private List<String> moduleUuids; private List<String> moduleUuids;
private Boolean onComponentOnly; private Boolean onComponentOnly;
private String branch; private String branch;
private String pullRequest;
private String organization; private String organization;
private Integer page; private Integer page;
private Integer pageSize; private Integer pageSize;
this.branch = branch; this.branch = branch;
return this; 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 ファイルの表示

static final String FIELD_PROJECT_VERSION = "projectVersion"; static final String FIELD_PROJECT_VERSION = "projectVersion";
static final String FIELD_ASSIGNEE = "assignee"; static final String FIELD_ASSIGNEE = "assignee";
static final String FIELD_BRANCH = "branch"; static final String FIELD_BRANCH = "branch";
static final String FIELD_PULL_REQUEST = "pullRequest";


protected final EmailSettings settings; protected final EmailSettings settings;
protected final I18n i18n; protected final I18n i18n;
} }
String projectName = checkNotNull(notification.getFieldValue(FIELD_PROJECT_NAME)); String projectName = checkNotNull(notification.getFieldValue(FIELD_PROJECT_NAME));
String branchName = notification.getFieldValue(FIELD_BRANCH); String branchName = notification.getFieldValue(FIELD_BRANCH);
String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);


StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.append("Project: ").append(projectName).append(NEW_LINE); message.append("Project: ").append(projectName).append(NEW_LINE);
if (branchName != null) { if (branchName != null) {
message.append("Branch: ").append(branchName).append(NEW_LINE); 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); String version = notification.getFieldValue(FIELD_PROJECT_VERSION);
if (version != null) { if (version != null) {
message.append("Version: ").append(version).append(NEW_LINE); message.append("Version: ").append(version).append(NEW_LINE);
if (branchName != null) { if (branchName != null) {
url += "&branch=" + encode(branchName); url += "&branch=" + encode(branchName);
} }
String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
if (pullRequest != null) {
url += "&pullRequest=" + encode(pullRequest);
}
url += "&createdAt=" + encode(DateUtils.formatDateTime(date)); url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message message
.append("More details at: ") .append("More details at: ")

+ 13
- 5
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangeNotification.java ファイルの表示

import org.sonar.core.issue.FieldDiffs; import org.sonar.core.issue.FieldDiffs;
import org.sonar.db.component.ComponentDto; 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 class IssueChangeNotification extends Notification {


public static final String TYPE = "issue-changes"; public static final String TYPE = "issue-changes";
} }


public IssueChangeNotification setProject(ComponentDto project) { 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) { if (branch != null) {
setFieldValue("branch", branch);
setFieldValue(FIELD_BRANCH, branch);
}
if (pullRequest != null) {
setFieldValue(FIELD_PULL_REQUEST, pullRequest);
} }
return this; return this;
} }

+ 12
- 2
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java ファイルの表示

import org.sonar.plugins.emailnotifications.api.EmailTemplate; import org.sonar.plugins.emailnotifications.api.EmailTemplate;


import static java.net.URLEncoder.encode; 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". * Creates email message for notification "issue-changes".


private static void appendHeader(Notification notif, StringBuilder sb) { private static void appendHeader(Notification notif, StringBuilder sb) {
appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey"))); appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey")));
String branchName = notif.getFieldValue("branch");
String branchName = notif.getFieldValue(FIELD_BRANCH);
if (branchName != null) { if (branchName != null) {
appendField(sb, "Branch", null, branchName); 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, "Rule", null, notif.getFieldValue("ruleName"));
appendField(sb, "Message", null, notif.getFieldValue("message")); appendField(sb, "Message", null, notif.getFieldValue("message"));
} }
.append("/project/issues?id=").append(encode(notification.getFieldValue("projectKey"), "UTF-8")) .append("/project/issues?id=").append(encode(notification.getFieldValue("projectKey"), "UTF-8"))
.append("&issues=").append(issueKey) .append("&issues=").append(issueKey)
.append("&open=").append(issueKey); .append("&open=").append(issueKey);
String branchName = notification.getFieldValue("branch");
String branchName = notification.getFieldValue(FIELD_BRANCH);
if (branchName != null) { if (branchName != null) {
sb.append("&branch=").append(branchName); sb.append("&branch=").append(branchName);
} }
String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
if (pullRequest != null) {
sb.append("&pullRequest=").append(pullRequest);
}
sb.append(NEW_LINE); sb.append(NEW_LINE);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Encoding not supported", e); throw new IllegalStateException("Encoding not supported", e);

+ 5
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java ファイルの表示

settings.getServerBaseURL(), settings.getServerBaseURL(),
encode(projectKey), encode(projectKey),
encode(assignee)); encode(assignee));
String branchName = notification.getFieldValue("branch");
String branchName = notification.getFieldValue(FIELD_BRANCH);
if (branchName != null) { if (branchName != null) {
url += "&branch=" + encode(branchName); url += "&branch=" + encode(branchName);
} }
String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
if (pullRequest != null) {
url += "&pullRequest=" + encode(pullRequest);
}
url += "&createdAt=" + encode(DateUtils.formatDateTime(date)); url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message message
.append("More details at: ") .append("More details at: ")

+ 5
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java ファイルの表示



import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH; 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_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_DATE;
import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY; import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY;
import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME; import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME;
return this; 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_NAME, projectName);
setFieldValue(FIELD_PROJECT_KEY, projectKey); setFieldValue(FIELD_PROJECT_KEY, projectKey);
if (branchName != null) { if (branchName != null) {
setFieldValue(FIELD_BRANCH, branchName); setFieldValue(FIELD_BRANCH, branchName);
} }
if (pullRequest != null) {
setFieldValue(FIELD_PULL_REQUEST, pullRequest);
}
return this; return this;
} }



+ 9
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java ファイルの表示

import static org.sonar.server.es.SearchOptions.MAX_LIMIT; 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_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT; import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; 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_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS; 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_REPORTERS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED;
.setInternal(true) .setInternal(true)
.setSince("6.6"); .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) action.createParam(PARAM_ORGANIZATION)
.setDescription("Organization key") .setDescription("Organization key")
.setRequired(false) .setRequired(false)
.setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS)) .setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS))
.setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY)) .setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY))
.setBranch(request.param(PARAM_BRANCH)) .setBranch(request.param(PARAM_BRANCH))
.setPullRequest(request.param(PARAM_PULL_REQUEST))
.setOrganization(request.param(PARAM_ORGANIZATION)) .setOrganization(request.param(PARAM_ORGANIZATION))
.setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPage(request.mandatoryParamAsInt(Param.PAGE))
.setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java ファイルの表示

issueBuilder.setOrganization(data.getOrganizationKey(component.getOrganizationUuid())); issueBuilder.setOrganization(data.getOrganizationKey(component.getOrganizationUuid()));
issueBuilder.setComponent(component.getKey()); issueBuilder.setComponent(component.getKey());
setNullable(component.getBranch(), issueBuilder::setBranch); setNullable(component.getBranch(), issueBuilder::setBranch);
setNullable(component.getPullRequest(), issueBuilder::setPullRequest);
ComponentDto project = data.getComponentByUuid(dto.getProjectUuid()); ComponentDto project = data.getComponentByUuid(dto.getProjectUuid());
if (project != null) { if (project != null) {
issueBuilder.setProject(project.getKey()); issueBuilder.setProject(project.getKey());
.setLongName(nullToEmpty(dto.longName())) .setLongName(nullToEmpty(dto.longName()))
.setEnabled(dto.isEnabled()); .setEnabled(dto.isEnabled());
setNullable(dto.getBranch(), builder::setBranch); setNullable(dto.getBranch(), builder::setBranch);
setNullable(dto.getPullRequest(), builder::setPullRequest);
String path = dto.path(); String path = dto.path();
// path is not applicable to the components that are not files. // path is not applicable to the components that are not files.
// Value must not be "" in this case. // Value must not be "" in this case.

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java ファイルの表示



@Override @Override
public QualityGate loadQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto project, BranchDto branch) { 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; return ShortLivingBranchQualityGate.GATE;
} }



+ 39
- 15
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java ファイルの表示

import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; 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.ACTION_COMPONENT;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_ID; 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_DEVELOPER_KEY;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KEYS; 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 { public class ComponentAction implements MeasuresWsAction {
private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE); private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE);
.setInternal(true) .setInternal(true)
.setSince("6.6"); .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); createMetricKeysParameter(action);
createAdditionalFieldsParameter(action); createAdditionalFieldsParameter(action);
createDeveloperParameters(action); createDeveloperParameters(action);
String componentKey = request.getComponent(); String componentKey = request.getComponent();
String componentId = request.getComponentId(); String componentId = request.getComponentId();
String branch = request.getBranch(); 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) { private Optional<ComponentDto> getReferenceComponent(DbSession dbSession, ComponentDto component) {
.setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID)) .setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID))
.setComponent(request.param(PARAM_COMPONENT)) .setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH)) .setBranch(request.param(PARAM_BRANCH))
.setPullRequest(request.param(PARAM_PULL_REQUEST))
.setAdditionalFields(request.paramAsStrings(PARAM_ADDITIONAL_FIELDS)) .setAdditionalFields(request.paramAsStrings(PARAM_ADDITIONAL_FIELDS))
.setMetricKeys(request.mandatoryParamAsStrings(PARAM_METRIC_KEYS)); .setMetricKeys(request.mandatoryParamAsStrings(PARAM_METRIC_KEYS));
checkRequest(!componentRequest.getMetricKeys().isEmpty(), "At least one metric key must be provided"); checkRequest(!componentRequest.getMetricKeys().isEmpty(), "At least one metric key must be provided");
private String componentId; private String componentId;
private String component; private String component;
private String branch; private String branch;
private String pullRequest;
private List<String> metricKeys; private List<String> metricKeys;
private List<String> additionalFields; private List<String> additionalFields;
private String developerId; private String developerId;
return this; return this;
} }


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

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

private List<String> getMetricKeys() { private List<String> getMetricKeys() {
return metricKeys; return metricKeys;
} }

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java ファイルの表示

.setName(component.name()) .setName(component.name())
.setQualifier(component.qualifier()); .setQualifier(component.qualifier());
Protobuf.setNullable(component.getBranch(), wsComponent::setBranch); Protobuf.setNullable(component.getBranch(), wsComponent::setBranch);
Protobuf.setNullable(component.getPullRequest(), wsComponent::setPullRequest);
Protobuf.setNullable(component.path(), wsComponent::setPath); Protobuf.setNullable(component.path(), wsComponent::setPath);
Protobuf.setNullable(component.description(), wsComponent::setDescription); Protobuf.setNullable(component.description(), wsComponent::setDescription);
Protobuf.setNullable(component.language(), wsComponent::setLanguage); Protobuf.setNullable(component.language(), wsComponent::setLanguage);

+ 31
- 19
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java ファイルの表示

import static org.sonar.db.component.ComponentTreeQuery.Strategy.CHILDREN; import static org.sonar.db.component.ComponentTreeQuery.Strategy.CHILDREN;
import static org.sonar.db.component.ComponentTreeQuery.Strategy.LEAVES; 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.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.ACTION_COMPONENT_TREE;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS; import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT; 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;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER; 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_QUALIFIERS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_STRATEGY; 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. * <p>Navigate through components based on different strategy with specified measures.
.setInternal(true) .setInternal(true)
.setSince("6.6"); .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) action.createParam(PARAM_METRIC_SORT)
.setDescription( .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, 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,
.setBaseComponentId(request.param(DEPRECATED_PARAM_BASE_COMPONENT_ID)) .setBaseComponentId(request.param(DEPRECATED_PARAM_BASE_COMPONENT_ID))
.setComponent(request.param(PARAM_COMPONENT)) .setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH)) .setBranch(request.param(PARAM_BRANCH))
.setPullRequest(request.param(PARAM_PULL_REQUEST))
.setMetricKeys(metricKeys) .setMetricKeys(metricKeys)
.setStrategy(request.mandatoryParam(PARAM_STRATEGY)) .setStrategy(request.mandatoryParam(PARAM_STRATEGY))
.setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS)) .setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS))
} }


private ComponentDto loadComponent(DbSession dbSession, ComponentTreeRequest request) { private ComponentDto loadComponent(DbSession dbSession, ComponentTreeRequest request) {
String componentKey = request.getComponent();
String componentId = request.getBaseComponentId(); String componentId = request.getBaseComponentId();
String componentKey = request.getComponent();
String branch = request.getBranch(); 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) { 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 ファイルの表示

private String baseComponentId; private String baseComponentId;
private String component; private String component;
private String branch; private String branch;
private String pullRequest;
private String strategy; private String strategy;
private List<String> qualifiers; private List<String> qualifiers;
private List<String> additionalFields; private List<String> additionalFields;
return this; return this;
} }


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

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

@CheckForNull @CheckForNull
public String getStrategy() { public String getStrategy() {
return strategy; return strategy;

+ 40
- 21
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java ファイルの表示

import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; 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.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.server.ws.KeyExamples; import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.Measures.SearchHistoryResponse; import org.sonarqube.ws.Measures.SearchHistoryResponse;


import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime; import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime; import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED; 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.ACTION_SEARCH_HISTORY;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH; 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_COMPONENT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_FROM; 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_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.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 { public class SearchHistoryAction implements MeasuresWsAction {


this.userSession = userSession; 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 @Override
public void define(WebService.NewController context) { public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_SEARCH_HISTORY) WebService.NewAction action = context.createAction(ACTION_SEARCH_HISTORY)
.setInternal(true) .setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .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) action.createParam(PARAM_METRICS)
.setDescription("Comma-separated list of metric keys") .setDescription("Comma-separated list of metric keys")
.setRequired(true) .setRequired(true)
writeProtobuf(searchHistoryResponse, request, response); 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() { private Function<SearchHistoryRequest, SearchHistoryResult> search() {
return request -> { return request -> {
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
private ComponentDto loadComponent(DbSession dbSession, SearchHistoryRequest request) { private ComponentDto loadComponent(DbSession dbSession, SearchHistoryRequest request) {
String componentKey = request.getComponent(); String componentKey = request.getComponent();
String branch = request.getBranch(); 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 { static class SearchHistoryRequest {
private final String component; private final String component;
private final String branch; private final String branch;
private final String pullRequest;
private final List<String> metrics; private final List<String> metrics;
private final String from; private final String from;
private final String to; private final String to;
public SearchHistoryRequest(Builder builder) { public SearchHistoryRequest(Builder builder) {
this.component = builder.component; this.component = builder.component;
this.branch = builder.branch; this.branch = builder.branch;
this.pullRequest = builder.pullRequest;
this.metrics = builder.metrics; this.metrics = builder.metrics;
this.from = builder.from; this.from = builder.from;
this.to = builder.to; this.to = builder.to;
return branch; return branch;
} }


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

public List<String> getMetrics() { public List<String> getMetrics() {
return metrics; return metrics;
} }
static class Builder { static class Builder {
private String component; private String component;
private String branch; private String branch;
private String pullRequest;
private List<String> metrics; private List<String> metrics;
private String from; private String from;
private String to; private String to;
return this; return this;
} }


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

public Builder setMetrics(List<String> metrics) { public Builder setMetrics(List<String> metrics) {
this.metrics = metrics; this.metrics = metrics;
return this; return this;

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java ファイルの表示

import org.sonar.server.badge.ws.ProjectBadgesWsModule; import org.sonar.server.badge.ws.ProjectBadgesWsModule;
import org.sonar.server.batch.BatchWsModule; import org.sonar.server.batch.BatchWsModule;
import org.sonar.server.branch.BranchFeatureProxyImpl; 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.branch.ws.BranchWsModule;
import org.sonar.server.ce.ws.CeWsModule; import org.sonar.server.ce.ws.CeWsModule;
import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentCleanerService;


// components // components
BranchWsModule.class, BranchWsModule.class,
PullRequestWsModule.class,
ProjectsWsModule.class, ProjectsWsModule.class,
ProjectsEsModule.class, ProjectsEsModule.class,
ProjectTagsWsModule.class, ProjectTagsWsModule.class,

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsParameters.java ファイルの表示

public static final String PARAM_FROM = "from"; public static final String PARAM_FROM = "from";
public static final String PARAM_TO = "to"; public static final String PARAM_TO = "to";
public static final String PARAM_BRANCH = "branch"; public static final String PARAM_BRANCH = "branch";
public static final String PARAM_PULL_REQUEST = "pullRequest";


private ProjectAnalysesWsParameters() { private ProjectAnalysesWsParameters() {
// static access only // static access only

+ 13
- 6
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java ファイルの表示

import static org.sonar.core.util.Protobuf.setNullable; 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_FIELD.BY_DATE;
import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC; 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.EventCategory.OTHER;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH; 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_CATEGORY;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_FROM; 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_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.ProjectAnalysesWsParameters.PARAM_TO;
import static org.sonar.server.projectanalysis.ws.SearchRequest.DEFAULT_PAGE_SIZE; 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 { public class SearchAction implements ProjectAnalysesWsAction {
private static final Set<String> ALLOWED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW); private static final Set<String> ALLOWED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW);
.setInternal(true) .setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001); .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) action.createParam(PARAM_CATEGORY)
.setDescription("Event category. Filter analyses that have at least one event of the category specified.") .setDescription("Event category. Filter analyses that have at least one event of the category specified.")
.setPossibleValues(EnumSet.allOf(EventCategory.class)) .setPossibleValues(EnumSet.allOf(EventCategory.class))
return SearchRequest.builder() return SearchRequest.builder()
.setProject(request.mandatoryParam(PARAM_PROJECT)) .setProject(request.mandatoryParam(PARAM_PROJECT))
.setBranch(request.param(PARAM_BRANCH)) .setBranch(request.param(PARAM_BRANCH))
.setPullRequest(request.param(PARAM_PULL_REQUEST))
.setCategory(category == null ? null : EventCategory.valueOf(category)) .setCategory(category == null ? null : EventCategory.valueOf(category))
.setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPage(request.mandatoryParamAsInt(Param.PAGE))
.setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
private ComponentDto loadComponent(DbSession dbSession, SearchRequest request) { private ComponentDto loadComponent(DbSession dbSession, SearchRequest request) {
String project = request.getProject(); String project = request.getProject();
String branch = request.getBranch(); 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 ファイルの表示


変更されたファイルが多すぎるため、一部のファイルは表示されません

読み込み中…
キャンセル
保存