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
+ 26 // level 1
+ 53 // content of DaoModule
+ 3 // content of EsModule
- + 57 // content of CorePropertyDefinitions
+ + 59 // content of CorePropertyDefinitions
+ 1 // StopFlagContainer
);
assertThat(
"UUID" VARCHAR(50) NOT NULL PRIMARY KEY,
"PROJECT_UUID" VARCHAR(50) NOT NULL,
"KEE" VARCHAR(255) NOT NULL,
- "BRANCH_TYPE" VARCHAR(5),
+ "KEY_TYPE" VARCHAR(12) NOT NULL,
+ "BRANCH_TYPE" VARCHAR(12),
"MERGE_BRANCH_UUID" VARCHAR(50),
+ "PULL_REQUEST_BINARY" BLOB,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
);
CREATE UNIQUE INDEX "PK_PROJECT_BRANCHES" ON "PROJECT_BRANCHES" ("UUID");
-CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE");
+CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE", "KEY_TYPE");
CREATE TABLE "ANALYSIS_PROPERTIES" (
"UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
public static final String BRANCH_KEY = "branch";
public static final String BRANCH_TYPE_KEY = "branchType";
+ public static final String PULL_REQUEST = "pullRequest";
private String uuid;
private String taskUuid;
}
public void insert(DbSession dbSession, BranchDto dto) {
+ setKeyType(dto);
mapper(dbSession).insert(dto, system2.now());
}
public void upsert(DbSession dbSession, BranchDto dto) {
BranchMapper mapper = mapper(dbSession);
long now = system2.now();
+ setKeyType(dto);
if (mapper.update(dto, now) == 0) {
mapper.insert(dto, now);
}
}
+ private static void setKeyType(BranchDto dto) {
+ if (dto.getBranchType() == BranchType.PULL_REQUEST) {
+ dto.setKeyType(KeyType.PULL_REQUEST);
+ } else {
+ dto.setKeyType(KeyType.BRANCH);
+ }
+ }
+
public int updateMainBranchName(DbSession dbSession, String projectUuid, String newBranchKey) {
long now = system2.now();
return mapper(dbSession).updateMainBranchName(projectUuid, newBranchKey, now);
}
- public Optional<BranchDto> selectByKey(DbSession dbSession, String projectUuid, String key) {
- return Optional.ofNullable(mapper(dbSession).selectByKey(projectUuid, key));
+ public Optional<BranchDto> selectByBranchKey(DbSession dbSession, String projectUuid, String key) {
+ return selectByKey(dbSession, projectUuid, key, KeyType.BRANCH);
+ }
+
+ public Optional<BranchDto> selectByPullRequestKey(DbSession dbSession, String projectUuid, String key) {
+ return selectByKey(dbSession, projectUuid, key, KeyType.PULL_REQUEST);
+ }
+
+ private static Optional<BranchDto> selectByKey(DbSession dbSession, String projectUuid, String key, KeyType keyType) {
+ return Optional.ofNullable(mapper(dbSession).selectByKey(projectUuid, key, keyType));
}
public Collection<BranchDto> selectByComponent(DbSession dbSession, ComponentDto component) {
*/
package org.sonar.db.component;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.Objects;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+import org.sonar.db.protobuf.DbProjectBranches;
import static com.google.common.base.Preconditions.checkArgument;
private String projectUuid;
/**
- * Name of branch, for example "feature/foo".
+ * Key that identifies a branch or a pull request.
+ * For keyType=BRANCH, this is the name of the branch, for example "feature/foo".
+ * For keyType=PULL_REQUEST, this is the ID of the pull request in some external system, for example 123 in GitHub.
*/
private String kee;
+ /**
+ * Key type, as provided by {@link KeyType}.
+ * Not null.
+ */
+ private KeyType keyType;
+
/**
* Branch type, as provided by {@link BranchType}.
* Not null.
@Nullable
private String mergeBranchUuid;
+ /**
+ * Pull Request data, such as branch name, title, url, and provider specific attributes
+ */
+ @Nullable
+ private byte[] pullRequestBinary;
+
public String getUuid() {
return uuid;
}
return this;
}
+ BranchDto setKeyType(@Nullable KeyType keyType) {
+ this.keyType = keyType;
+ return this;
+ }
+
public BranchType getBranchType() {
return branchType;
}
return this;
}
+ public BranchDto setPullRequestData(DbProjectBranches.PullRequestData pullRequestData) {
+ this.pullRequestBinary = encodePullRequestData(pullRequestData);
+ return this;
+ }
+
+ @CheckForNull
+ public DbProjectBranches.PullRequestData getPullRequestData() {
+ if (pullRequestBinary == null) {
+ return null;
+ }
+ return decodePullRequestData(pullRequestBinary);
+ }
+
+ private static byte[] encodePullRequestData(DbProjectBranches.PullRequestData pullRequestData) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ pullRequestData.writeTo(outputStream);
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to serialize pull request data", e);
+ }
+ }
+
+ private static DbProjectBranches.PullRequestData decodePullRequestData(byte[] pullRequestBinary) {
+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pullRequestBinary)) {
+ return DbProjectBranches.PullRequestData.parseFrom(inputStream);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to deserialize pull request data", e);
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
sb.append("uuid='").append(uuid).append('\'');
sb.append(", projectUuid='").append(projectUuid).append('\'');
sb.append(", kee='").append(kee).append('\'');
+ sb.append(", keyType=").append(keyType);
sb.append(", branchType=").append(branchType);
sb.append(", mergeBranchUuid='").append(mergeBranchUuid).append('\'');
sb.append('}');
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);
/**
* Short-lived branch
*/
- SHORT
+ SHORT,
+
+ /**
+ * Pull request
+ */
+ PULL_REQUEST
}
import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
import static org.sonar.db.component.ComponentDto.generateBranchKey;
+import static org.sonar.db.component.ComponentDto.generatePullRequestKey;
public class ComponentDao implements Dao {
return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, branch));
}
+ public List<ComponentDto> selectByKeysAndPullRequest(DbSession session, Collection<String> keys, String pullRequestId) {
+ List<String> dbKeys = keys.stream().map(k -> generatePullRequestKey(k, pullRequestId)).collect(toList());
+ List<String> allKeys = Stream.of(keys, dbKeys).flatMap(Collection::stream).collect(toList());
+ return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, pullRequestId));
+ }
+
public List<ComponentDto> selectComponentsHavingSameKeyOrderedById(DbSession session, String key) {
return mapper(session).selectComponentsHavingSameKeyOrderedById(key);
}
}
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) {
import com.google.common.base.Strings;
import java.util.Date;
import java.util.List;
+import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.sonar.api.resources.Scopes;
import org.sonar.db.WildcardPosition;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.substringBeforeLast;
import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.component.ComponentValidator.checkComponentKey;
import static org.sonar.db.component.ComponentValidator.checkComponentName;
* Separator used to generate the key of the branch
*/
public static final String BRANCH_KEY_SEPARATOR = ":BRANCH:";
+ public static final String PULL_REQUEST_SEPARATOR = ":PULL_REQUEST:";
+ private static final Splitter BRANCH_OR_PULL_REQUEST_SPLITTER = Splitter.on(Pattern.compile(BRANCH_KEY_SEPARATOR + "|" + PULL_REQUEST_SEPARATOR));
private static final Splitter BRANCH_KEY_SPLITTER = Splitter.on(BRANCH_KEY_SEPARATOR);
+ private static final Splitter PULL_REQUEST_SPLITTER = Splitter.on(PULL_REQUEST_SEPARATOR);
public static final String UUID_PATH_SEPARATOR = ".";
public static final String UUID_PATH_OF_ROOT = UUID_PATH_SEPARATOR;
private String organizationUuid;
/**
- * Non-empty and unique functional key
+ * Non-empty and unique functional key. Do not rename, used by MyBatis.
*/
private String kee;
return UUID_PATH_SPLITTER.splitToList(uuidPath);
}
- /**
- * Used my MyBatis mapper
- */
- private String getKee(){
- return kee;
- }
-
- /**
- * Used my MyBatis mapper
- */
- private void setKee(String kee){
- this.kee = kee;
- }
-
public String getDbKey() {
return kee;
}
* The key to be displayed to user, doesn't contain information on branches
*/
public String getKey() {
- List<String> split = BRANCH_KEY_SPLITTER.splitToList(kee);
+ List<String> split = BRANCH_OR_PULL_REQUEST_SPLITTER.splitToList(kee);
return split.size() == 2 ? split.get(0) : kee;
}
return split.size() == 2 ? split.get(1) : null;
}
+ /**
+ * @return the pull request id. It will be null when the component is not on a pull request
+ */
+ @CheckForNull
+ public String getPullRequest() {
+ List<String> split = PULL_REQUEST_SPLITTER.splitToList(kee);
+ return split.size() == 2 ? split.get(1) : null;
+ }
+
public String scope() {
return scope;
}
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);
+ }
}
private static String branchBaseKey(String key) {
int index = key.lastIndexOf(ComponentDto.BRANCH_KEY_SEPARATOR);
- if (index == -1) {
- return key;
+ if (index > -1) {
+ return key.substring(0, index);
}
- return key.substring(0, index);
+ index = key.lastIndexOf(ComponentDto.PULL_REQUEST_SEPARATOR);
+ if (index > -1) {
+ return key.substring(0, index);
+ }
+ return key;
}
private static void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper) {
ComponentDto selectByKey(String key);
@CheckForNull
- ComponentDto selectByKeyAndBranch(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
+ ComponentDto selectByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
@CheckForNull
ComponentDto selectById(long id);
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+/*
+ 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;
+}
pb.uuid as uuid,
pb.project_uuid as projectUuid,
pb.kee as kee,
+ pb.key_type as keyType,
pb.branch_type as branchType,
- pb.merge_branch_uuid as mergeBranchUuid
+ pb.merge_branch_uuid as mergeBranchUuid,
+ pb.pull_request_binary as pullRequestBinary
</sql>
<insert id="insert" parameterType="map" useGeneratedKeys="false">
uuid,
project_uuid,
kee,
+ key_type,
branch_type,
merge_branch_uuid,
+ pull_request_binary,
created_at,
updated_at
) values (
#{dto.uuid, jdbcType=VARCHAR},
#{dto.projectUuid, jdbcType=VARCHAR},
#{dto.kee, jdbcType=VARCHAR},
+ #{dto.keyType, jdbcType=VARCHAR},
#{dto.branchType, jdbcType=VARCHAR},
#{dto.mergeBranchUuid, jdbcType=VARCHAR},
+ #{dto.pullRequestBinary, jdbcType=BINARY},
#{now, jdbcType=BIGINT},
#{now, jdbcType=BIGINT}
)
update project_branches
set
merge_branch_uuid = #{dto.mergeBranchUuid, jdbcType=VARCHAR},
+ pull_request_binary = #{dto.pullRequestBinary, jdbcType=BINARY},
updated_at = #{now, jdbcType=BIGINT}
where
uuid = #{dto.uuid, jdbcType=VARCHAR}
from project_branches pb
where
pb.project_uuid = #{projectUuid, jdbcType=VARCHAR} and
- pb.kee = #{key, jdbcType=VARCHAR}
+ pb.kee = #{key, jdbcType=VARCHAR} and
+ pb.key_type = #{keyType, jdbcType=VARCHAR}
</select>
<select id="selectByProjectUuid" parameterType="string" resultType="org.sonar.db.component.BranchDto">
p.kee=#{key,jdbcType=VARCHAR}
</select>
- <select id="selectByKeyAndBranch" parameterType="String" resultType="Component">
+ <select id="selectByKeyAndBranchKey" parameterType="String" resultType="Component">
SELECT
<include refid="componentColumns"/>
FROM projects p
ON p.uuid = i.component_uuid
JOIN project_branches b
ON i.project_uuid = b.uuid
- AND b.branch_type = 'SHORT'
+ AND (b.branch_type = 'SHORT' OR b.branch_type = 'PULL_REQUEST')
AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR}
AND i.status != 'CLOSED'
</select>
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
+import org.sonar.db.protobuf.DbProjectBranches;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
private static final long NOW = 1_000L;
private static final String SELECT_FROM = "select project_uuid as \"projectUuid\", uuid as \"uuid\", branch_type as \"branchType\", " +
- "kee as \"kee\", merge_branch_uuid as \"mergeBranchUuid\", created_at as \"createdAt\", updated_at as \"updatedAt\" " +
+ "kee as \"kee\", merge_branch_uuid as \"mergeBranchUuid\", pull_request_binary as \"pullRequestBinary\", created_at as \"createdAt\", updated_at as \"updatedAt\" " +
"from project_branches ";
private System2 system2 = new TestSystem2().setNow(NOW);
entry("branchType", "SHORT"),
entry("kee", "feature/foo"),
entry("mergeBranchUuid", null),
+ entry("pullRequestBinary", null),
entry("createdAt", 1_000L),
entry("updatedAt", 1_000L));
}
underTest.insert(dbSession, dto2);
underTest.updateMainBranchName(dbSession, "U1", "master");
- BranchDto loaded = underTest.selectByKey(dbSession, "U1", "master").get();
+ BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "master").get();
assertThat(loaded.getMergeBranchUuid()).isNull();
assertThat(loaded.getProjectUuid()).isEqualTo("U1");
assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG);
}
@Test
- public void upsert() {
+ public void insert_pull_request_branch_with_only_non_null_fields() {
+ String projectUuid = "U1";
+ String uuid = "U2";
+ BranchType branchType = BranchType.PULL_REQUEST;
+ String kee = "123";
+
+ BranchDto dto = new BranchDto();
+ dto.setProjectUuid(projectUuid);
+ dto.setUuid(uuid);
+ dto.setBranchType(branchType);
+ dto.setKey(kee);
+
+ underTest.insert(dbSession, dto);
+
+ BranchDto loaded = underTest.selectByUuid(dbSession, dto.getUuid()).get();
+
+ assertThat(loaded.getProjectUuid()).isEqualTo(projectUuid);
+ assertThat(loaded.getUuid()).isEqualTo(uuid);
+ assertThat(loaded.getBranchType()).isEqualTo(branchType);
+ assertThat(loaded.getKey()).isEqualTo(kee);
+ assertThat(loaded.getMergeBranchUuid()).isNull();
+ assertThat(loaded.getPullRequestData()).isNull();
+ }
+
+ @Test
+ public void insert_pull_request_branch_with_all_fields() {
+ String projectUuid = "U1";
+ String uuid = "U2";
+ BranchType branchType = BranchType.PULL_REQUEST;
+ String kee = "123";
+
+ String branch = "feature/pr1";
+ String title = "Dummy Feature Title";
+ String url = "http://example.com/pullRequests/pr1";
+ String tokenAttributeName = "token";
+ String tokenAttributeValue = "dummy token";
+ DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch(branch)
+ .setTitle(title)
+ .setUrl(url)
+ .putAttributes(tokenAttributeName, tokenAttributeValue)
+ .build();
+
+ BranchDto dto = new BranchDto();
+ dto.setProjectUuid(projectUuid);
+ dto.setUuid(uuid);
+ dto.setBranchType(branchType);
+ dto.setKey(kee);
+ dto.setPullRequestData(pullRequestData);
+
+ underTest.insert(dbSession, dto);
+
+ BranchDto loaded = underTest.selectByUuid(dbSession, dto.getUuid()).get();
+
+ assertThat(loaded.getProjectUuid()).isEqualTo(projectUuid);
+ assertThat(loaded.getUuid()).isEqualTo(uuid);
+ assertThat(loaded.getBranchType()).isEqualTo(branchType);
+ assertThat(loaded.getKey()).isEqualTo(kee);
+ assertThat(loaded.getMergeBranchUuid()).isNull();
+
+ DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData();
+ assertThat(loadedPullRequestData).isNotNull();
+ assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
+ assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
+ assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
+ assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ }
+
+ @Test
+ public void upsert_branch() {
BranchDto dto = new BranchDto();
dto.setProjectUuid("U1");
dto.setUuid("U2");
dto.setBranchType(BranchType.SHORT);
underTest.upsert(dbSession, dto);
- BranchDto loaded = underTest.selectByKey(dbSession, "U1", "foo").get();
+ BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "foo").get();
assertThat(loaded.getMergeBranchUuid()).isEqualTo("U3");
assertThat(loaded.getProjectUuid()).isEqualTo("U1");
assertThat(loaded.getBranchType()).isEqualTo(BranchType.LONG);
}
@Test
- public void selectByKey() {
+ public void upsert_pull_request() {
+ BranchDto dto = new BranchDto();
+ dto.setProjectUuid("U1");
+ dto.setUuid("U2");
+ dto.setBranchType(BranchType.PULL_REQUEST);
+ dto.setKey("foo");
+ underTest.insert(dbSession, dto);
+
+ // the fields that can be updated
+ dto.setMergeBranchUuid("U3");
+
+ String branch = "feature/pr1";
+ String title = "Dummy Feature Title";
+ String url = "http://example.com/pullRequests/pr1";
+ String tokenAttributeName = "token";
+ String tokenAttributeValue = "dummy token";
+ DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch(branch)
+ .setTitle(title)
+ .setUrl(url)
+ .putAttributes(tokenAttributeName, tokenAttributeValue)
+ .build();
+ dto.setPullRequestData(pullRequestData);
+
+ // the fields that can't be updated. New values are ignored.
+ dto.setProjectUuid("ignored");
+ dto.setBranchType(BranchType.SHORT);
+ underTest.upsert(dbSession, dto);
+
+ BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", "foo").get();
+ assertThat(loaded.getMergeBranchUuid()).isEqualTo("U3");
+ assertThat(loaded.getProjectUuid()).isEqualTo("U1");
+ assertThat(loaded.getBranchType()).isEqualTo(BranchType.PULL_REQUEST);
+
+ DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData();
+ assertThat(loadedPullRequestData).isNotNull();
+ assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
+ assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
+ assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
+ assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ }
+
+ @Test
+ public void update_pull_request_data() {
+ BranchDto dto = new BranchDto();
+ dto.setProjectUuid("U1");
+ dto.setUuid("U2");
+ dto.setBranchType(BranchType.PULL_REQUEST);
+ dto.setKey("foo");
+
+ // the fields that can be updated
+ String mergeBranchUuid = "U3";
+ dto.setMergeBranchUuid(mergeBranchUuid + "-dummy-suffix");
+
+ String branch = "feature/pr1";
+ String title = "Dummy Feature Title";
+ String url = "http://example.com/pullRequests/pr1";
+ String tokenAttributeName = "token";
+ String tokenAttributeValue = "dummy token";
+ DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch(branch + "-dummy-suffix")
+ .setTitle(title + "-dummy-suffix")
+ .setUrl(url + "-dummy-suffix")
+ .putAttributes(tokenAttributeName, tokenAttributeValue + "-dummy-suffix")
+ .build();
+ dto.setPullRequestData(pullRequestData);
+
+ underTest.insert(dbSession, dto);
+
+ // modify pull request data
+
+ dto.setMergeBranchUuid(mergeBranchUuid);
+ pullRequestData = DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch(branch)
+ .setTitle(title)
+ .setUrl(url)
+ .putAttributes(tokenAttributeName, tokenAttributeValue)
+ .build();
+ dto.setPullRequestData(pullRequestData);
+
+ underTest.upsert(dbSession, dto);
+
+ BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", "foo").get();
+ assertThat(loaded.getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
+ assertThat(loaded.getProjectUuid()).isEqualTo("U1");
+ assertThat(loaded.getBranchType()).isEqualTo(BranchType.PULL_REQUEST);
+
+ DbProjectBranches.PullRequestData loadedPullRequestData = loaded.getPullRequestData();
+ assertThat(loadedPullRequestData).isNotNull();
+ assertThat(loadedPullRequestData.getBranch()).isEqualTo(branch);
+ assertThat(loadedPullRequestData.getTitle()).isEqualTo(title);
+ assertThat(loadedPullRequestData.getUrl()).isEqualTo(url);
+ assertThat(loadedPullRequestData.getAttributesMap().get(tokenAttributeName)).isEqualTo(tokenAttributeValue);
+ }
+
+ @Test
+ public void selectByBranchKey() {
BranchDto mainBranch = new BranchDto();
mainBranch.setProjectUuid("U1");
mainBranch.setUuid("U1");
underTest.insert(dbSession, featureBranch);
// select the feature branch
- BranchDto loaded = underTest.selectByKey(dbSession, "U1", "feature/foo").get();
+ BranchDto loaded = underTest.selectByBranchKey(dbSession, "U1", "feature/foo").get();
assertThat(loaded.getUuid()).isEqualTo(featureBranch.getUuid());
assertThat(loaded.getKey()).isEqualTo(featureBranch.getKey());
assertThat(loaded.getProjectUuid()).isEqualTo(featureBranch.getProjectUuid());
assertThat(loaded.getMergeBranchUuid()).isEqualTo(featureBranch.getMergeBranchUuid());
// select a branch on another project with same branch name
- assertThat(underTest.selectByKey(dbSession, "U3", "feature/foo")).isEmpty();
+ assertThat(underTest.selectByBranchKey(dbSession, "U3", "feature/foo")).isEmpty();
+ }
+
+ @Test
+ public void selectByPullRequestKey() {
+ BranchDto mainBranch = new BranchDto();
+ mainBranch.setProjectUuid("U1");
+ mainBranch.setUuid("U1");
+ mainBranch.setBranchType(BranchType.LONG);
+ mainBranch.setKey("master");
+ underTest.insert(dbSession, mainBranch);
+
+ String pullRequestId = "123";
+ BranchDto pullRequest = new BranchDto();
+ pullRequest.setProjectUuid("U1");
+ pullRequest.setUuid("U2");
+ pullRequest.setBranchType(BranchType.PULL_REQUEST);
+ pullRequest.setKey(pullRequestId);
+ pullRequest.setMergeBranchUuid("U3");
+ underTest.insert(dbSession, pullRequest);
+
+ // select the feature branch
+ BranchDto loaded = underTest.selectByPullRequestKey(dbSession, "U1", pullRequestId).get();
+ assertThat(loaded.getUuid()).isEqualTo(pullRequest.getUuid());
+ assertThat(loaded.getKey()).isEqualTo(pullRequest.getKey());
+ assertThat(loaded.getProjectUuid()).isEqualTo(pullRequest.getProjectUuid());
+ assertThat(loaded.getBranchType()).isEqualTo(pullRequest.getBranchType());
+ assertThat(loaded.getMergeBranchUuid()).isEqualTo(pullRequest.getMergeBranchUuid());
+
+ // select a branch on another project with same branch name
+ assertThat(underTest.selectByPullRequestKey(dbSession, "U3", pullRequestId)).isEmpty();
}
@Test
*/
package org.sonar.db.component;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.protobuf.DbProjectBranches;
import static org.assertj.core.api.Assertions.assertThat;
public class BranchDtoTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
private BranchDto underTest = new BranchDto();
@Test
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();
+ }
}
assertThat(underTest.getKey()).isEqualTo("my_key");
assertThat(underTest.getBranch()).isNull();
}
+
+ @Test
+ public void getKey_and_getPullRequest() {
+ ComponentDto underTest = new ComponentDto().setDbKey("my_key:PULL_REQUEST:pr-123");
+ assertThat(underTest.getKey()).isEqualTo("my_key");
+ assertThat(underTest.getPullRequest()).isEqualTo("pr-123");
+
+ underTest = new ComponentDto().setDbKey("my_key");
+ assertThat(underTest.getKey()).isEqualTo("my_key");
+ assertThat(underTest.getPullRequest()).isNull();
+ }
}
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentKeyUpdaterDao.computeNewKey;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
}
+ @Test
+ public void updateKey_updates_pull_requests_too() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ db.components().insertComponent(newFileDto(pullRequest));
+ db.components().insertComponent(newFileDto(pullRequest));
+ int branchComponentCount = 3;
+
+ String oldProjectKey = project.getKey();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1);
+
+ String oldBranchKey = pullRequest.getDbKey();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).hasSize(branchComponentCount);
+
+ String newProjectKey = "newKey";
+ String newBranchKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest());
+ underTest.updateKey(dbSession, project.uuid(), newProjectKey);
+
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).isEmpty();
+
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1);
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newBranchKey)).hasSize(branchComponentCount);
+ db.select(dbSession, "select kee from projects")
+ .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
+ }
+
@Test
public void bulk_updateKey_updates_branches_too() {
ComponentDto project = db.components().insertMainBranch();
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
}
+ @Test
+ public void bulk_updateKey_updates_pull_requests_too() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(pullRequest), project.getKey()));
+ db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+ db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+ int branchComponentCount = 4;
+
+ String oldProjectKey = project.getKey();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1);
+
+ String oldPullRequestKey = pullRequest.getDbKey();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).hasSize(branchComponentCount);
+
+ String newProjectKey = "newKey";
+ String newPullRequestKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest());
+ underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey);
+
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty();
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).isEmpty();
+
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1);
+ assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newPullRequestKey)).hasSize(branchComponentCount);
+ db.select(dbSession, "select kee from projects")
+ .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
+ }
+
private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) {
return componentDto.setDbKey(key + ":" + componentDto.getDbKey());
}
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
public class ComponentTesting {
private static String generateKey(String key, ComponentDto parentModuleOrProject) {
String branch = parentModuleOrProject.getBranch();
- return branch == null ? key : ComponentDto.generateBranchKey(key, branch);
+ if (branch != null) {
+ return ComponentDto.generateBranchKey(key, branch);
+ }
+ String pullRequest = parentModuleOrProject.getPullRequest();
+ if (pullRequest != null) {
+ return ComponentDto.generatePullRequestKey(key, pullRequest);
+ }
+
+ return key;
}
public static ComponentDto newModuleDto(ComponentDto subProjectOrProject) {
checkArgument(project.qualifier().equals(Qualifiers.PROJECT));
checkArgument(project.getMainBranchProjectUuid() == null);
String branchName = branchDto.getKey();
+ String branchSeparator = branchDto.getBranchType() == PULL_REQUEST ? ":PULL_REQUEST:" : ":BRANCH:";
String uuid = branchDto.getUuid();
return new ComponentDto()
.setUuid(uuid)
.setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR)
.setRootUuid(uuid)
// name of the branch is not mandatory on the main branch
- .setDbKey(branchName != null ? project.getDbKey() + ":BRANCH:" + branchName : project.getKey())
+ .setDbKey(branchName != null ? project.getDbKey() + branchSeparator + branchName : project.getKey())
.setMainBranchProjectUuid(project.uuid())
.setName(project.name())
.setLongName(project.longName())
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
.add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class)
.add(2014, "Migrate webhooks from SETTINGS table to WEBHOOKS table", MigrateWebhooksToWebhooksTable.class)
.add(2015, "Add webhook key to WEBHOOK_DELIVERIES table", AddWebhookKeyToWebhookDeliveriesTable.class)
+ .add(2016, "Increase branch type size in PROJECT_BRANCHES", IncreaseBranchTypeSizeForPullRequest.class)
+ .add(2017, "Add key_type column in PROJECT_BRANCHES", AddKeyTypeInProjectBranches.class)
+ .add(2018, "Fill key_type column in PROJECT_BRANCHES", SetKeyTypeToBranchInProjectBranches.class)
+ .add(2019, "Make key_type not nullable in PROJECT_BRANCHES", MakeKeyTypeNotNullableInProjectBranches.class)
+ .add(2020, "Replace index in PROJECT_BRANCHES", ReplaceIndexInProjectBranches.class)
+ .add(2021, "Add pull_request_data in PROJECT_BRANCHES", AddPullRequestBinaryInProjectBranches.class)
;
}
}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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()
+ );
+ }
+}
--- /dev/null
+/*
+ * 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;
+ });
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 16);
+ verifyMigrationCount(underTest, 22);
}
}
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+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");
--- /dev/null
+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");
--- /dev/null
+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");
--- /dev/null
+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");
--- /dev/null
+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");
--- /dev/null
+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");
import org.sonar.api.config.Settings;
import org.sonar.api.config.internal.ConfigurationBridge;
import org.sonar.db.DbClient;
+import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.settings.ChildSettings;
import static org.sonar.db.component.ComponentDto.generateBranchKey;
+import static org.sonar.db.component.ComponentDto.generatePullRequestKey;
@ComputeEngineSide
public class ProjectConfigurationFactory {
public Configuration newProjectConfiguration(String projectKey, Branch branch) {
Settings projectSettings = new ChildSettings(globalSettings);
addSettings(projectSettings, projectKey);
- addSettings(projectSettings, generateBranchKey(projectKey, branch.getName()));
+ if (branch.getType() == BranchType.PULL_REQUEST) {
+ addSettings(projectSettings, generatePullRequestKey(projectKey, branch.getPullRequestId()));
+ } else {
+ addSettings(projectSettings, generateBranchKey(projectKey, branch.getName()));
+ }
return new ConfigurationBridge(projectSettings);
}
import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.Metric.Level;
import static org.sonar.api.measures.Metric.ValueType;
import static org.sonar.api.measures.Metric.Level.ERROR;
import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.valueOf;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonarqube.ws.MediaTypes.SVG;
public class MeasureAction implements ProjectBadgesWsAction {
private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private static final String PARAM_METRIC = "metric";
private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder()
.createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
action.createParam(PARAM_METRIC)
.setDescription("Metric key")
.setRequired(true)
response.stream().setMediaType(SVG);
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
String metricKey = request.mandatoryParam(PARAM_METRIC);
try (DbSession dbSession = dbClient.openSession(false)) {
- ComponentDto project = componentFinder.getByKeyAndOptionalBranch(dbSession, projectKey, branch);
+ ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest);
userSession.checkComponentPermission(USER, project);
MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey);
checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", metricKey);
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonarqube.ws.MediaTypes.SVG;
public class QualityGateAction implements ProjectBadgesWsAction {
private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private final UserSession userSession;
private final DbClient dbClient;
.createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
response.stream().setMediaType(SVG);
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
try (DbSession dbSession = dbClient.openSession(false)) {
- ComponentDto project = componentFinder.getByKeyAndOptionalBranch(dbSession, projectKey, branch);
+ ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest);
userSession.checkComponentPermission(USER, project);
Level qualityGateStatus = getQualityGate(dbSession, project);
write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8);
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class ProjectAction implements BatchWsAction {
private static final String PARAM_PROFILE = "profile";
private static final String PARAM_ISSUES_MODE = "issues_mode";
private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private final ProjectDataLoader projectDataLoader;
.setSince("6.6")
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setSince("7.1")
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
.setModuleKey(wsRequest.mandatoryParam(PARAM_KEY))
.setProfileName(wsRequest.param(PARAM_PROFILE))
.setIssuesMode(wsRequest.mandatoryParamAsBoolean(PARAM_ISSUES_MODE))
- .setBranch(wsRequest.param(PARAM_BRANCH)));
+ .setBranch(wsRequest.param(PARAM_BRANCH))
+ .setPullRequest(wsRequest.param(PARAM_PULL_REQUEST)));
WsProjectResponse projectResponse = buildResponse(data);
writeProtobuf(projectResponse, wsRequest, wsResponse);
ProjectRepositories data = new ProjectRepositories();
String moduleKey = query.getModuleKey();
String branch = query.getBranch();
+ String pullRequest = query.getPullRequest();
ComponentDto mainModule = componentFinder.getByKey(session, moduleKey);
checkRequest(isProjectOrModule(mainModule), "Key '%s' belongs to a component which is not a Project", moduleKey);
boolean hasScanPerm = userSession.hasComponentPermission(SCAN_EXECUTION, mainModule) ||
userSession.hasPermission(OrganizationPermission.SCAN, mainModule.getOrganizationUuid());
boolean hasBrowsePerm = userSession.hasComponentPermission(USER, mainModule);
checkPermission(query.isIssuesMode(), hasScanPerm, hasBrowsePerm);
- ComponentDto branchOrMainModule = branch == null ? mainModule : componentFinder.getByKeyAndBranch(session, moduleKey, branch);
+ ComponentDto branchOrMainModule = (branch == null && pullRequest == null) ? mainModule
+ : componentFinder.getByKeyAndOptionalBranchOrPullRequest(session, moduleKey, branch, pullRequest);
ComponentDto project = getProject(branchOrMainModule, session);
if (!project.getKey().equals(branchOrMainModule.getKey())) {
private String profileName;
private boolean issuesMode;
private String branch;
+ private String pullRequest;
private ProjectDataQuery() {
// No direct call
return this;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public ProjectDataQuery setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
public static ProjectDataQuery create() {
return new ProjectDataQuery();
}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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
+ }
+}
--- /dev/null
+/*
+ * 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;
*/
package org.sonar.server.branch.ws;
-import com.google.common.io.Resources;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import static org.sonar.server.branch.ws.BranchesWs.addBranchParam;
import static org.sonar.server.branch.ws.BranchesWs.addProjectParam;
-import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.ACTION_DELETE;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_BRANCH;
import static org.sonar.server.branch.ws.ProjectBranchesParameters.PARAM_PROJECT;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
public class DeleteAction implements BranchWsAction {
private final DbClient dbClient;
.setSince("6.6")
.setDescription("Delete a non-main branch of a project.<br/>" +
"Requires 'Administer' rights on the specified project.")
- .setResponseExample(Resources.getResource(getClass(), "list-example.json"))
.setPost(true)
.setHandler(this);
checkPermission(project);
BranchDto branch = checkFoundWithOptional(
- dbClient.branchDao().selectByKey(dbSession, project.uuid(), branchKey),
+ dbClient.branchDao().selectByBranchKey(dbSession, project.uuid(), branchKey),
"Branch '%s' not found for project '%s'", branchKey, projectKey);
if (branch.isMain()) {
import com.google.common.io.Resources;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
-import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.UserRole;
import org.sonar.core.util.Protobuf;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
checkPermission(project);
checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key");
- Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project);
+ Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project).stream()
+ .filter(b -> b.getBranchType() == SHORT || b.getBranchType() == LONG)
+ .collect(MoreCollectors.toList());
+ List<String> branchUuids = branches.stream().map(BranchDto::getUuid).collect(toList());
+
Map<String, BranchDto> mergeBranchesByUuid = dbClient.branchDao()
.selectByUuids(dbSession, branches.stream().map(BranchDto::getMergeBranchUuid).filter(Objects::nonNull).collect(toList()))
.stream().collect(uniqueIndex(BranchDto::getUuid));
Map<String, LiveMeasureDto> qualityGateMeasuresByComponentUuids = dbClient.liveMeasureDao()
- .selectByComponentUuidsAndMetricKeys(dbSession, branches.stream().map(BranchDto::getUuid).collect(toList()), singletonList(ALERT_STATUS_KEY))
- .stream().collect(uniqueIndex(LiveMeasureDto::getComponentUuid));
+ .selectByComponentUuidsAndMetricKeys(dbSession, branchUuids, singletonList(ALERT_STATUS_KEY)).stream()
+ .collect(uniqueIndex(LiveMeasureDto::getComponentUuid));
Map<String, BranchStatistics> branchStatisticsByBranchUuid = issueIndex.searchBranchStatistics(project.uuid(), branches.stream()
.filter(b -> b.getBranchType().equals(SHORT))
- .map(BranchDto::getUuid).collect(toList()))
- .stream().collect(uniqueIndex(BranchStatistics::getBranchUuid, Function.identity()));
+ .map(BranchDto::getUuid).collect(toList())).stream()
+ .collect(uniqueIndex(BranchStatistics::getBranchUuid, Function.identity()));
Map<String, String> analysisDateByBranchUuid = dbClient.snapshotDao()
- .selectLastAnalysesByRootComponentUuids(dbSession, branches.stream().map(BranchDto::getUuid).collect(Collectors.toList()))
- .stream().collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> formatDateTime(s.getCreatedAt())));
+ .selectLastAnalysesByRootComponentUuids(dbSession, branchUuids).stream()
+ .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> formatDateTime(s.getCreatedAt())));
ProjectBranches.ListWsResponse.Builder protobufResponse = ProjectBranches.ListWsResponse.newBuilder();
branches.forEach(b -> addBranch(protobufResponse, b, mergeBranchesByUuid, qualityGateMeasuresByComponentUuids.get(b.getUuid()), branchStatisticsByBranchUuid.get(b.getUuid()),
- analysisDateByBranchUuid.get(b.getUuid())));
+ analysisDateByBranchUuid.get(b.getUuid())));
WsUtils.writeProtobuf(protobufResponse.build(), request, response);
}
}
private static void addBranch(ProjectBranches.ListWsResponse.Builder response, BranchDto branch, Map<String, BranchDto> mergeBranchesByUuid,
- @Nullable LiveMeasureDto qualityGateMeasure, BranchStatistics branchStatistics, @Nullable String analysisDate) {
+ @Nullable LiveMeasureDto qualityGateMeasure, BranchStatistics branchStatistics, @Nullable String analysisDate) {
ProjectBranches.Branch.Builder builder = toBranchBuilder(branch, Optional.ofNullable(mergeBranchesByUuid.get(branch.getMergeBranchUuid())));
setBranchStatus(builder, branch, qualityGateMeasure, branchStatistics);
if (analysisDate != null) {
setNullable(branchKey, builder::setName);
builder.setIsMain(branch.isMain());
builder.setType(Common.BranchType.valueOf(branch.getBranchType().name()));
- if (branch.getBranchType().equals(SHORT)) {
+ if (branch.getBranchType() == SHORT) {
if (mergeBranch.isPresent()) {
String mergeBranchKey = mergeBranch.get().getKey();
builder.setMergeBranch(mergeBranchKey);
}
private static void setBranchStatus(ProjectBranches.Branch.Builder builder, BranchDto branch, @Nullable LiveMeasureDto qualityGateMeasure,
- @Nullable BranchStatistics branchStatistics) {
- ProjectBranches.Branch.Status.Builder statusBuilder = ProjectBranches.Branch.Status.newBuilder();
+ @Nullable BranchStatistics branchStatistics) {
+ ProjectBranches.Status.Builder statusBuilder = ProjectBranches.Status.newBuilder();
if (branch.getBranchType() == LONG && qualityGateMeasure != null) {
Protobuf.setNullable(qualityGateMeasure.getDataAsString(), statusBuilder::setQualityGateStatus);
}
ComponentDto project = componentFinder.getRootComponentByUuidOrKey(dbSession, null, projectKey);
checkPermission(project);
- Optional<BranchDto> existingBranch = dbClient.branchDao().selectByKey(dbSession, project.uuid(), newBranchName);
+ Optional<BranchDto> existingBranch = dbClient.branchDao().selectByBranchKey(dbSession, project.uuid(), newBranchName);
checkArgument(!existingBranch.filter(b -> !b.isMain()).isPresent(),
"Impossible to update branch name: a branch with name \"%s\" already exists in the project.", newBranchName);
.setChangelog(
new Change("5.5", "it's no more possible to specify the page parameter."),
new Change("6.1", "field \"logs\" is deprecated and its value is always false"),
- new Change("6.6", "fields \"branch\" and \"branchType\" added"))
+ new Change("6.6", "fields \"branch\" and \"branchType\" added"),
+ new Change("7.1", "fields \"pullRequest\" and \"pullRequestTitle\" added"))
.setSince("5.2");
action.createParam(PARAM_COMPONENT_ID)
builder.setSubmittedAt(formatDateTime(new Date(dto.getCreatedAt())));
setNullable(dto.getStartedAt(), builder::setStartedAt, DateUtils::formatDateTime);
setNullable(computeExecutionTimeMs(dto), builder::setExecutionTimeMs);
- setBranch(builder, dto.getUuid(), componentDtoCache);
+ setBranchOrPullRequest(builder, dto.getUuid(), componentDtoCache);
return builder.build();
}
builder.setLogs(false);
setNullable(dto.getComponentUuid(), uuid -> setComponent(builder, uuid, componentDtoCache).setComponentId(uuid));
String analysisUuid = dto.getAnalysisUuid();
- if (analysisUuid != null) {
- builder.setAnalysisId(analysisUuid);
- }
- setBranch(builder, dto.getUuid(), componentDtoCache);
+ setNullable(analysisUuid, builder::setAnalysisId);
+ setBranchOrPullRequest(builder, dto.getUuid(), componentDtoCache);
setNullable(analysisUuid, builder::setAnalysisId);
setNullable(dto.getSubmitterLogin(), builder::setSubmitterLogin);
builder.setSubmittedAt(formatDateTime(new Date(dto.getSubmittedAt())));
return builder;
}
- private static Ce.Task.Builder setBranch(Ce.Task.Builder builder, String taskUuid, DtoCache componentDtoCache) {
- componentDtoCache.getBranchName(taskUuid).ifPresent(
+ private static Ce.Task.Builder setBranchOrPullRequest(Ce.Task.Builder builder, String taskUuid, DtoCache componentDtoCache) {
+ componentDtoCache.getBranchKey(taskUuid).ifPresent(
b -> {
- builder.setBranch(b);
- builder.setBranchType(componentDtoCache.getBranchType(taskUuid)
- .orElseThrow(() -> new IllegalStateException(format("Could not find branch type of task '%s'", taskUuid))));
+ Common.BranchType branchType = componentDtoCache.getBranchType(taskUuid)
+ .orElseThrow(() -> new IllegalStateException(format("Could not find branch type of task '%s'", taskUuid)));
+ switch (branchType) {
+ case LONG:
+ case SHORT:
+ builder.setBranchType(branchType);
+ builder.setBranch(b);
+ break;
+ default:
+ throw new IllegalStateException(String.format("Unknown branch type '%s'", branchType));
+ }
});
+ componentDtoCache.getPullRequest(taskUuid).ifPresent(builder::setPullRequest);
return builder;
}
return organizationDto.getKey();
}
- Optional<String> getBranchName(String taskUuid) {
+ Optional<String> getBranchKey(String taskUuid) {
return characteristicsByTaskUuid.get(taskUuid).stream()
.filter(c -> c.getKey().equals(CeTaskCharacteristicDto.BRANCH_KEY))
.map(CeTaskCharacteristicDto::getValue)
.map(c -> Common.BranchType.valueOf(c.getValue()))
.findAny();
}
+
+ Optional<String> getPullRequest(String taskUuid) {
+ return characteristicsByTaskUuid.get(taskUuid).stream()
+ .filter(c -> c.getKey().equals(CeTaskCharacteristicDto.PULL_REQUEST))
+ .map(CeTaskCharacteristicDto::getValue)
+ .findAny();
+ }
}
/**
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 {
UUID_AND_KEY("uuid", "key"),
ID_AND_KEY("id", "key"),
COMPONENT_ID_AND_KEY("componentId", "componentKey"),
- BASE_COMPONENT_ID_AND_KEY("baseComponentId", "baseComponentKey"),
+ BASE_COMPONENT_ID_AND_KEY("baseComponentId", "component"),
DEVELOPER_ID_AND_KEY("developerId", "developerKey"),
COMPONENT_ID_AND_COMPONENT("componentId", "component"),
PROJECT_ID_AND_PROJECT("projectId", "project"),
import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
public class AppAction implements ComponentsWsAction {
.setSince("6.6")
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setSince("7.1")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
private ComponentDto loadComponent(DbSession dbSession, Request request) {
String componentUuid = request.param(PARAM_COMPONENT_ID);
- String branch = request.param("branch");
- checkArgument(componentUuid == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH);
- if (branch == null) {
+ String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
+ checkArgument(componentUuid == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID,
+ PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param(PARAM_COMPONENT), COMPONENT_ID_AND_COMPONENT);
}
- return componentFinder.getByKeyAndOptionalBranch(dbSession, request.mandatoryParam(PARAM_COMPONENT), branch);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, request.mandatoryParam(PARAM_COMPONENT), branch, pullRequest);
}
private void appendComponent(JsonWriter json, ComponentDto component, UserSession userSession, DbSession session) {
if (branch != null) {
json.prop("branch", branch);
}
+ String pullRequest = project.getPullRequest();
+ if (pullRequest != null) {
+ json.prop("pullRequest", pullRequest);
+ }
json.prop("fav", isFavourite);
}
.setName(dto.name())
.setQualifier(dto.qualifier());
setNullable(emptyToNull(dto.getBranch()), wsComponent::setBranch);
+ setNullable(emptyToNull(dto.getPullRequest()), wsComponent::setPullRequest);
setNullable(emptyToNull(dto.path()), wsComponent::setPath);
setNullable(emptyToNull(dto.description()), wsComponent::setDescription);
setNullable(emptyToNull(dto.language()), wsComponent::setLanguage);
public static final String DEPRECATED_PARAM_BASE_COMPONENT_KEY = "baseComponentKey";
public static final String PARAM_COMPONENT = "component";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
public static final String PARAM_STRATEGY = "strategy";
public static final String PARAM_QUALIFIERS = "qualifiers";
public static final String PARAM_METRICS = "metrics";
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Components.ShowWsResponse;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT;
import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID;
-import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
public class ShowAction implements ComponentsWsAction {
private final UserSession userSession;
.setExampleValue(KEY_BRANCH_EXAMPLE_001)
.setInternal(true)
.setSince("6.6");
+
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
}
@Override
private ShowWsResponse doHandle(Request request) {
try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto component = loadComponent(dbSession, request);
+ userSession.checkComponentPermission(UserRole.USER, component);
Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.projectUuid());
List<ComponentDto> ancestors = dbClient.componentDao().selectAncestors(dbSession, component);
OrganizationDto organizationDto = componentFinder.getOrganization(dbSession, component);
String componentId = request.getId();
String componentKey = request.getKey();
String branch = request.getBranch();
- checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH);
- ComponentDto component = branch == null
- ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT)
- : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
- userSession.checkComponentPermission(UserRole.USER, component);
- return component;
+ String pullRequest = request.getPullRequest();
+ checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID,
+ PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT);
+ }
+ checkRequest(componentKey!=null, "The '%s' parameter is missing", PARAM_COMPONENT);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
private static ShowWsResponse buildResponse(ComponentDto component, OrganizationDto organizationDto, List<ComponentDto> orderedAncestors, Optional<SnapshotDto> lastAnalysis) {
return new Request()
.setId(request.param(PARAM_COMPONENT_ID))
.setKey(request.param(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH));
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST));
}
private static class Request {
private String id;
private String key;
private String branch;
+ private String pullRequest;
@CheckForNull
public String getId() {
this.branch = branch;
return this;
}
+
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public Request setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
}
}
import static org.sonar.server.component.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
+import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
+import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_TREE;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY;
-import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
public class TreeAction implements ComponentsWsAction {
.setInternal(true)
.setSince("6.6");
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
+
action.createSortParams(SORTS, NAME_SORT, true)
.setDescription("Comma-separated list of sort fields")
.setExampleValue(NAME_SORT + ", " + PATH_SORT);
private ComponentDto loadComponent(DbSession dbSession, Request request) {
String componentId = request.getBaseComponentId();
- String componentKey = request.getBaseComponentKey();
+ String componentKey = request.getComponent();
String branch = request.getBranch();
- checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_COMPONENT_ID, PARAM_BRANCH);
- return branch == null
- ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT)
- : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
+ String pullRequest = request.getPullRequest();
+ checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_COMPONENT_ID,
+ PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT);
+ }
+ checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
private Map<String, ComponentDto> searchReferenceComponentsByUuid(DbSession dbSession, List<ComponentDto> components) {
private static Request toTreeWsRequest(org.sonar.api.server.ws.Request request) {
return new Request()
.setBaseComponentId(request.param(PARAM_COMPONENT_ID))
- .setBaseComponentKey(request.param(PARAM_COMPONENT))
+ .setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
.setStrategy(request.mandatoryParam(PARAM_STRATEGY))
.setQuery(request.param(Param.TEXT_QUERY))
.setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS))
private static class Request {
private String baseComponentId;
- private String baseComponentKey;
private String component;
private String branch;
+ private String pullRequest;
private String strategy;
private List<String> qualifiers;
private String query;
return this;
}
- /**
- * @deprecated since 6.4, please use {@link #getComponent()} instead
- */
- @Deprecated
- @CheckForNull
- private String getBaseComponentKey() {
- return baseComponentKey;
- }
-
- /**
- * @deprecated since 6.4, please use {@link #setComponent(String)} instead
- */
- @Deprecated
- private Request setBaseComponentKey(@Nullable String baseComponentKey) {
- this.baseComponentKey = baseComponentKey;
- return this;
- }
-
public Request setComponent(@Nullable String component) {
this.component = component;
return this;
return this;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public Request setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
@CheckForNull
private String getStrategy() {
return strategy;
*/
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
*/
*/
Branch getBranch();
+ /**
+ * In a pull request analysis, return the ID of the pull request
+ *
+ * @throws IllegalStateException if current analysis is not a pull request
+ */
+ String getPullRequestId();
+
/**
* The project as represented by the main branch. It is used to load settings
* like Quality gates, webhooks and configuration.
* Plugins used during the analysis on scanner side
*/
Map<String, ScannerPlugin> getScannerPluginsByKey();
-
}
private final InitializedProperty<Analysis> baseProjectSnapshot = new InitializedProperty<>();
private final InitializedProperty<Boolean> crossProjectDuplicationEnabled = new InitializedProperty<>();
private final InitializedProperty<Branch> branch = new InitializedProperty<>();
+ private final InitializedProperty<String> pullRequestId = new InitializedProperty<>();
private final InitializedProperty<Project> project = new InitializedProperty<>();
private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>();
private final InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>();
return branch.getProperty();
}
+ @Override
+ public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) {
+ checkState(!this.pullRequestId.isInitialized(), "Pull request id has already been set");
+ this.pullRequestId.setProperty(pullRequestId);
+ return this;
+ }
+
+ @Override
+ public String getPullRequestId() {
+ checkState(pullRequestId.isInitialized(), "Pull request id has not been set");
+ return pullRequestId.getProperty();
+ }
+
@Override
public MutableAnalysisMetadataHolder setProject(Project project) {
checkState(!this.project.isInitialized(), "Project has already been set");
return pluginsByKey.getProperty();
}
+ @Override
public boolean isShortLivingBranch() {
checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
Branch prop = branch.getProperty();
return prop != null && prop.getType() == BranchType.SHORT;
}
+ @Override
public boolean isLongLivingBranch() {
checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
Branch prop = branch.getProperty();
return prop != null && prop.getType() == BranchType.LONG;
}
+ @Override
+ public boolean isPullRequest() {
+ checkState(this.branch.isInitialized(), BRANCH_NOT_SET);
+ Branch prop = branch.getProperty();
+ return prop != null && prop.getType() == BranchType.PULL_REQUEST;
+ }
+
}
* or not.
*/
boolean supportsCrossProjectCpd();
+
+ /**
+ * @throws IllegalStateException if this branch configuration is not a pull request.
+ */
+ String getPullRequestId();
}
*/
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
*/
import static java.util.Optional.ofNullable;
import static org.sonar.api.ce.posttask.CeTask.Status.FAILED;
import static org.sonar.api.ce.posttask.CeTask.Status.SUCCESS;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
/**
* Responsible for calling {@link PostProjectAnalysisTask} implementations (if any).
private BranchImpl createBranch() {
org.sonar.server.computation.task.projectanalysis.analysis.Branch analysisBranch = analysisMetadataHolder.getBranch();
if (!analysisBranch.isLegacyFeature()) {
- return new BranchImpl(analysisBranch.isMain(), analysisBranch.getName(), Branch.Type.valueOf(analysisBranch.getType().name()));
+ String branchKey = analysisBranch.getType() == PULL_REQUEST ? analysisBranch.getPullRequestId() : analysisBranch.getName();
+ return new BranchImpl(analysisBranch.isMain(), branchKey, Branch.Type.valueOf(analysisBranch.getType().name()));
}
return null;
}
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.protobuf.DbProjectBranches;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
if (branch.isMain()) {
checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis");
branchComponentDto = branchComponentDtoOpt.get();
-
} else {
// inserts new row in table projects if it's the first time branch is analyzed
branchComponentDto = branchComponentDtoOpt.or(() -> insertIntoProjectsTable(dbSession, branchUuid));
-
}
+
// insert or update in table project_branches
dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch));
}
return (first != null) ? first : second;
}
- private static BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
+ private BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
BranchDto dto = new BranchDto();
dto.setUuid(componentDto.uuid());
+
// MainBranchProjectUuid will be null if it's a main branch
dto.setProjectUuid(firstNonNull(componentDto.getMainBranchProjectUuid(), componentDto.projectUuid()));
- dto.setKey(branch.getName());
dto.setBranchType(branch.getType());
+
// merge branch is only present if it's a short living branch
dto.setMergeBranchUuid(branch.getMergeBranchUuid().orElse(null));
+
+ if (branch.getType() == BranchType.PULL_REQUEST) {
+ dto.setKey(analysisMetadataHolder.getPullRequestId());
+
+ DbProjectBranches.PullRequestData pullRequestData = DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch(branch.getName())
+ .setTitle(branch.getName())
+ .build();
+ dto.setPullRequestData(pullRequestData);
+ } else {
+ dto.setKey(branch.getName());
+ }
+
return dto;
}
return !isLegacyBranch;
}
+ @Override
+ public String getPullRequestId() {
+ throw new IllegalStateException("Only a branch of type PULL_REQUEST can have a pull request id.");
+ }
+
@Override
public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) {
String moduleWithBranch = module.getKey();
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import static com.google.common.base.Preconditions.checkState;
-import static org.sonar.db.component.ComponentDto.removeBranchFromKey;
+import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey;
/**
* Cache a map between component keys and uuids in the merge branch
@CheckForNull
public String getUuid(String dbKey) {
lazyInit();
- String cleanComponentKey = removeBranchFromKey(dbKey);
+ String cleanComponentKey = removeBranchAndPullRequestFromKey(dbKey);
return uuidsByKey.get(cleanComponentKey);
}
}
import org.sonar.db.DbSession;
import org.sonar.db.component.KeyWithUuidDto;
-import static org.sonar.db.component.ComponentDto.removeBranchFromKey;
+import static org.sonar.db.component.ComponentDto.removeBranchAndPullRequestFromKey;
/**
* Cache a map of component key -> uuid in short branches that have issues with status either RESOLVED or CONFIRMED.
try (DbSession dbSession = dbClient.openSession(false)) {
List<KeyWithUuidDto> components = dbClient.componentDao().selectComponentKeysHavingIssuesToMerge(dbSession, uuid);
for (KeyWithUuidDto dto : components) {
- uuidsByKey.computeIfAbsent(removeBranchFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
+ uuidsByKey.computeIfAbsent(removeBranchAndPullRequestFromKey(dto.key()), s -> new HashSet<>()).add(dto.uuid());
}
}
}
}
public TrackingResult track(Component component) {
- if (analysisMetadataHolder.isShortLivingBranch()) {
+ if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) {
return standardResult(shortBranchTracker.track(component));
} else if (isFirstAnalysisSecondaryLongLivingBranch()) {
Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component);
}
public Collection<ShortBranchIssue> loadCandidateIssuesForMergingInTargetBranch(Component component) {
- String componentKey = ComponentDto.removeBranchFromKey(component.getKey());
+ String componentKey = ComponentDto.removeBranchAndPullRequestFromKey(component.getKey());
Set<String> uuids = shortBranchComponentsWithIssues.getUuids(componentKey);
if (uuids.isEmpty()) {
return Collections.emptyList();
}
private Optional<QualityGate> getShortLivingBranchQualityGate() {
- if (analysisMetadataHolder.isShortLivingBranch()) {
+ if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) {
Optional<QualityGate> qualityGate = qualityGateService.findById(ShortLivingBranchQualityGate.ID);
if (qualityGate.isPresent()) {
return qualityGate;
@Override
public void execute() {
- // no notification on short living branch as there is no real Quality Gate on those
- if (analysisMetadataHolder.isShortLivingBranch()) {
+ // no notification on short living branch and pull request as there is no real Quality Gate on those
+ if (analysisMetadataHolder.isShortLivingBranch() || analysisMetadataHolder.isPullRequest()) {
return;
}
new DepthTraversalTypeAwareCrawler(
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
+import javax.annotation.CheckForNull;
import org.sonar.api.issue.Issue;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.issue.notification.NewIssuesStatistics;
import org.sonar.server.notification.NotificationService;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
/**
IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
changeNotification.setIssue(issue);
- changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName());
+ changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest());
getComponentKey(issue).ifPresent(c -> changeNotification.setComponent(c.getPublicKey(), c.getName()));
service.deliver(changeNotification);
}
NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics();
NewIssuesNotification notification = newIssuesNotificationFactory
.newNewIssuesNotication()
- .setProject(project.getPublicKey(), project.getName(), getBranchName())
+ .setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest())
.setProjectVersion(project.getReportAttributes().getVersion())
.setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), globalStatistics)
.newMyNewIssuesNotification()
.setAssignee(assignee);
myNewIssuesNotification
- .setProject(project.getPublicKey(), project.getName(), getBranchName())
+ .setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest())
.setProjectVersion(project.getReportAttributes().getVersion())
.setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), assigneeStatistics)
return "Send issue notifications";
}
+ @CheckForNull
private String getBranchName() {
Branch branch = analysisMetadataHolder.getBranch();
- return branch.isMain() ? null : branch.getName();
+ return branch.isMain() || branch.getType() == PULL_REQUEST ? null : branch.getName();
+ }
+
+ @CheckForNull
+ private String getPullRequest() {
+ Branch branch = analysisMetadataHolder.getBranch();
+ return branch.getType() == PULL_REQUEST ? analysisMetadataHolder.getPullRequestId() : null;
}
}
this.componentDao = componentDao;
}
- public List<Block> parse(DbSession session, ComponentDto component, @Nullable String branch, @Nullable String duplicationsData) {
+ public List<Block> parse(DbSession session, ComponentDto component, @Nullable String branch, @Nullable String pullRequest, @Nullable String duplicationsData) {
Map<String, ComponentDto> componentsByKey = newHashMap();
List<Block> blocks = newArrayList();
if (duplicationsData != null) {
String size = bCursor.getAttrValue("l");
String componentKey = bCursor.getAttrValue("r");
if (from != null && size != null && componentKey != null) {
- duplications.add(createDuplication(componentsByKey, branch, from, size, componentKey, session));
+ duplications.add(createDuplication(componentsByKey, branch, pullRequest, from, size, componentKey, session));
}
}
Collections.sort(duplications, new DuplicationComparator(component.uuid(), component.projectUuid()));
return blocks;
}
- private Duplication createDuplication(Map<String, ComponentDto> componentsByKey, @Nullable String branch, String from, String size, String componentDbKey, DbSession session) {
+ private Duplication createDuplication(Map<String, ComponentDto> componentsByKey, @Nullable String branch, @Nullable String pullRequest, String from, String size,
+ String componentDbKey, DbSession session) {
String componentKey = convertToKey(componentDbKey);
ComponentDto component = componentsByKey.get(componentKey);
if (component == null) {
- Optional<ComponentDto> componentDtoOptional = branch == null ? componentDao.selectByKey(session, componentKey)
- : Optional.fromNullable(componentDao.selectByKeyAndBranch(session, componentKey, branch).orElseGet(null));
+ Optional<ComponentDto> componentDtoOptional;
+ if (branch != null) {
+ componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndBranch(session, componentKey, branch).orElseGet(null));
+ } else if (pullRequest != null) {
+ componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndPullRequest(session, componentKey, pullRequest).orElseGet(null));
+ } else {
+ componentDtoOptional = componentDao.selectByKey(session, componentKey);
+ }
component = componentDtoOptional.isPresent() ? componentDtoOptional.get() : null;
componentsByKey.put(componentKey, component);
}
import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
-import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
public class ShowAction implements DuplicationsWsAction {
+ private static final String PARAM_KEY = "key";
+ private static final String PARAM_UUID = "uuid";
+ private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private final DbClient dbClient;
private final DuplicationsParser parser;
private final ShowResponseBuilder responseBuilder;
new Change("6.5", "The fields 'uuid', 'projectUuid', 'subProjectUuid' are deprecated in the response."));
action
- .createParam("key")
+ .createParam(PARAM_KEY)
.setDescription("File key")
.setExampleValue("my_project:/src/foo/Bar.php");
action
- .createParam("uuid")
+ .createParam(PARAM_UUID)
.setDeprecatedSince("6.5")
.setDescription("File ID. If provided, 'key' must not be provided.")
.setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2");
action
- .createParam("branch")
+ .createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setInternal(true)
+ .setSince("7.1")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
userSession.checkComponentPermission(UserRole.CODEVIEWER, component);
String duplications = findDataFromComponent(dbSession, component);
String branch = component.getBranch();
- List<DuplicationsParser.Block> blocks = parser.parse(dbSession, component, branch, duplications);
- writeProtobuf(responseBuilder.build(dbSession, blocks, branch), request, response);
+ String pullRequest = component.getPullRequest();
+ List<DuplicationsParser.Block> blocks = parser.parse(dbSession, component, branch, pullRequest, duplications);
+ writeProtobuf(responseBuilder.build(dbSession, blocks, branch, pullRequest), request, response);
}
}
private ComponentDto loadComponent(DbSession dbSession, Request request) {
- String componentUuid = request.param("uuid");
- String branch = request.param("branch");
- checkArgument(componentUuid == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", "uuid", PARAM_BRANCH);
- if (branch == null) {
- return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param("key"), UUID_AND_KEY);
+ String componentUuid = request.param(PARAM_UUID);
+ String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
+ checkArgument(componentUuid == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", PARAM_UUID,
+ PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentUuid, request.param(PARAM_KEY), UUID_AND_KEY);
}
- return componentFinder.getByKeyAndOptionalBranch(dbSession, request.mandatoryParam("key"), branch);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, request.mandatoryParam(PARAM_KEY), branch, pullRequest);
}
@CheckForNull
this.componentDao = componentDao;
}
- ShowResponse build(DbSession session, List<DuplicationsParser.Block> blocks, @Nullable String branch) {
+ ShowResponse build(DbSession session, List<DuplicationsParser.Block> blocks, @Nullable String branch, @Nullable String pullRequest) {
ShowResponse.Builder response = ShowResponse.newBuilder();
Map<String, String> refByComponentKey = newHashMap();
blocks.stream()
.map(block -> toWsDuplication(block, refByComponentKey))
.forEach(response::addDuplications);
- writeFiles(session, response, refByComponentKey, branch);
+ writeFiles(session, response, refByComponentKey, branch, pullRequest);
return response.build();
}
return block;
}
- private static Duplications.File toWsFile(ComponentDto file, @Nullable ComponentDto project, @Nullable ComponentDto subProject, @Nullable String branch) {
+ private static Duplications.File toWsFile(ComponentDto file, @Nullable ComponentDto project, @Nullable ComponentDto subProject, @Nullable String branch,
+ @Nullable String pullRequest) {
Duplications.File.Builder wsFile = Duplications.File.newBuilder();
wsFile.setKey(file.getKey());
wsFile.setUuid(file.uuid());
wsFile.setSubProjectUuid(subProject.uuid());
wsFile.setSubProjectName(subProject.longName());
}
- if (branch != null) {
- wsFile.setBranch(branch);
- }
+ setNullable(branch, wsFile::setBranch);
+ setNullable(pullRequest, wsFile::setPullRequest);
return wsFile;
});
return wsFile.build();
}
- private void writeFiles(DbSession session, ShowResponse.Builder response, Map<String, String> refByComponentKey, @Nullable String branch) {
+ private void writeFiles(DbSession session, ShowResponse.Builder response, Map<String, String> refByComponentKey, @Nullable String branch, @Nullable String pullRequest) {
Map<String, ComponentDto> projectsByUuid = newHashMap();
Map<String, ComponentDto> parentModulesByUuid = newHashMap();
Map<String, Duplications.File> filesByRef = response.getMutableFiles();
ComponentDto project = getProject(file.projectUuid(), projectsByUuid, session);
ComponentDto parentModule = getParentProject(file.getRootUuid(), parentModulesByUuid, session);
- filesByRef.put(ref, toWsFile(file, project, parentModule, branch));
+ filesByRef.put(ref, toWsFile(file, project, parentModule, branch, pullRequest));
}
}
}
Collection<String> componentRootUuids = request.getComponentRootUuids();
Collection<String> componentRoots = request.getComponentRoots();
String branch = request.getBranch();
+ String pullRequest = request.getPullRequest();
boolean effectiveOnComponentOnly = false;
if (componentRootUuids != null) {
allComponents.addAll(getComponentsFromUuids(session, componentRootUuids));
} else if (componentRoots != null) {
- allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch));
+ allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch, pullRequest));
} else if (components != null) {
- allComponents.addAll(getComponentsFromKeys(session, components, branch));
+ allComponents.addAll(getComponentsFromKeys(session, components, branch, pullRequest));
effectiveOnComponentOnly = true;
} else if (componentUuids != null) {
allComponents.addAll(getComponentsFromUuids(session, componentUuids));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
} else if (componentKeys != null) {
- allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch));
+ allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch, pullRequest));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
}
.count() <= 1;
}
- private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly,
- List<ComponentDto> components, SearchRequest request) {
-
+ private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly, List<ComponentDto> components, SearchRequest request) {
builder.onComponentOnly(onComponentOnly);
if (onComponentOnly) {
builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
- setBranch(builder, components.get(0), request.getBranch());
+ setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
return;
}
if (projectUuids != null) {
builder.projectUuids(projectUuids);
} else if (projectKeys != null) {
- List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch());
+ List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, request.getBranch(), request.getPullRequest());
builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList()));
- setBranch(builder, projects.get(0), request.getBranch());
+ setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest());
}
builder.moduleUuids(request.getModuleUuids());
builder.directories(request.getDirectories());
Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet());
checkArgument(qualifiers.size() == 1, "All components must have the same qualifier, found %s", String.join(",", qualifiers));
- setBranch(builder, components.get(0), request.getBranch());
+ setBranch(builder, components.get(0), request.getBranch(), request.getPullRequest());
String qualifier = qualifiers.iterator().next();
switch (qualifier) {
case Qualifiers.VIEW:
builder.directories(directoryPaths);
}
- private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch) {
- List<ComponentDto> componentDtos = branch == null
- ? dbClient.componentDao().selectByKeys(dbSession, componentKeys)
- : dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch);
+ private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch, @Nullable String pullRequest) {
+ List<ComponentDto> componentDtos;
+ if (branch != null) {
+ componentDtos = dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch);
+ } else if (pullRequest != null) {
+ componentDtos = dbClient.componentDao().selectByKeysAndPullRequest(dbSession, componentKeys, pullRequest);
+ } else {
+ componentDtos = dbClient.componentDao().selectByKeys(dbSession, componentKeys);
+ }
if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
return singletonList(UNKNOWN_COMPONENT);
}
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())));
}
}
private List<String> moduleUuids;
private Boolean onComponentOnly;
private String branch;
+ private String pullRequest;
private String organization;
private Integer page;
private Integer pageSize;
this.branch = branch;
return this;
}
+
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public SearchRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
}
static final String FIELD_PROJECT_VERSION = "projectVersion";
static final String FIELD_ASSIGNEE = "assignee";
static final String FIELD_BRANCH = "branch";
+ static final String FIELD_PULL_REQUEST = "pullRequest";
protected final EmailSettings settings;
protected final I18n i18n;
}
String projectName = checkNotNull(notification.getFieldValue(FIELD_PROJECT_NAME));
String branchName = notification.getFieldValue(FIELD_BRANCH);
+ String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
StringBuilder message = new StringBuilder();
message.append("Project: ").append(projectName).append(NEW_LINE);
if (branchName != null) {
message.append("Branch: ").append(branchName).append(NEW_LINE);
}
+ if (pullRequest!= null) {
+ message.append("Pull request: ").append(pullRequest).append(NEW_LINE);
+ }
String version = notification.getFieldValue(FIELD_PROJECT_VERSION);
if (version != null) {
message.append("Version: ").append(version).append(NEW_LINE);
if (branchName != null) {
url += "&branch=" + encode(branchName);
}
+ String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
+ if (pullRequest != null) {
+ url += "&pullRequest=" + encode(pullRequest);
+ }
url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message
.append("More details at: ")
import org.sonar.core.issue.FieldDiffs;
import org.sonar.db.component.ComponentDto;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_KEY;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_NAME;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
+
public class IssueChangeNotification extends Notification {
public static final String TYPE = "issue-changes";
}
public IssueChangeNotification setProject(ComponentDto project) {
- return setProject(project.getKey(), project.name(), project.getBranch());
+ return setProject(project.getKey(), project.name(), project.getBranch(), project.getPullRequest());
}
- public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch) {
- setFieldValue("projectName", projectName);
- setFieldValue("projectKey", projectKey);
+ public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch, @Nullable String pullRequest) {
+ setFieldValue(FIELD_PROJECT_NAME, projectName);
+ setFieldValue(FIELD_PROJECT_KEY, projectKey);
if (branch != null) {
- setFieldValue("branch", branch);
+ setFieldValue(FIELD_BRANCH, branch);
+ }
+ if (pullRequest != null) {
+ setFieldValue(FIELD_PULL_REQUEST, pullRequest);
}
return this;
}
import org.sonar.plugins.emailnotifications.api.EmailTemplate;
import static java.net.URLEncoder.encode;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
/**
* Creates email message for notification "issue-changes".
private static void appendHeader(Notification notif, StringBuilder sb) {
appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey")));
- String branchName = notif.getFieldValue("branch");
+ String branchName = notif.getFieldValue(FIELD_BRANCH);
if (branchName != null) {
appendField(sb, "Branch", null, branchName);
}
+ String pullRequest = notif.getFieldValue(FIELD_PULL_REQUEST);
+ if (pullRequest != null) {
+ appendField(sb, "Pull request", null, pullRequest);
+ }
appendField(sb, "Rule", null, notif.getFieldValue("ruleName"));
appendField(sb, "Message", null, notif.getFieldValue("message"));
}
.append("/project/issues?id=").append(encode(notification.getFieldValue("projectKey"), "UTF-8"))
.append("&issues=").append(issueKey)
.append("&open=").append(issueKey);
- String branchName = notification.getFieldValue("branch");
+ String branchName = notification.getFieldValue(FIELD_BRANCH);
if (branchName != null) {
sb.append("&branch=").append(branchName);
}
+ String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
+ if (pullRequest != null) {
+ sb.append("&pullRequest=").append(pullRequest);
+ }
sb.append(NEW_LINE);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Encoding not supported", e);
settings.getServerBaseURL(),
encode(projectKey),
encode(assignee));
- String branchName = notification.getFieldValue("branch");
+ String branchName = notification.getFieldValue(FIELD_BRANCH);
if (branchName != null) {
url += "&branch=" + encode(branchName);
}
+ String pullRequest = notification.getFieldValue(FIELD_PULL_REQUEST);
+ if (pullRequest != null) {
+ url += "&pullRequest=" + encode(pullRequest);
+ }
url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message
.append("More details at: ")
import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_DATE;
import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY;
import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME;
return this;
}
- public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName) {
+ public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName, @Nullable String pullRequest) {
setFieldValue(FIELD_PROJECT_NAME, projectName);
setFieldValue(FIELD_PROJECT_KEY, projectKey);
if (branchName != null) {
setFieldValue(FIELD_BRANCH, branchName);
}
+ if (pullRequest != null) {
+ setFieldValue(FIELD_PULL_REQUEST, pullRequest);
+ }
return this;
}
import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_REPORTERS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED;
.setInternal(true)
.setSince("6.6");
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
+
action.createParam(PARAM_ORGANIZATION)
.setDescription("Organization key")
.setRequired(false)
.setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS))
.setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY))
.setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
.setOrganization(request.param(PARAM_ORGANIZATION))
.setPage(request.mandatoryParamAsInt(Param.PAGE))
.setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
issueBuilder.setOrganization(data.getOrganizationKey(component.getOrganizationUuid()));
issueBuilder.setComponent(component.getKey());
setNullable(component.getBranch(), issueBuilder::setBranch);
+ setNullable(component.getPullRequest(), issueBuilder::setPullRequest);
ComponentDto project = data.getComponentByUuid(dto.getProjectUuid());
if (project != null) {
issueBuilder.setProject(project.getKey());
.setLongName(nullToEmpty(dto.longName()))
.setEnabled(dto.isEnabled());
setNullable(dto.getBranch(), builder::setBranch);
+ setNullable(dto.getPullRequest(), builder::setPullRequest);
String path = dto.path();
// path is not applicable to the components that are not files.
// Value must not be "" in this case.
@Override
public QualityGate loadQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto project, BranchDto branch) {
- if (branch.getBranchType() == BranchType.SHORT) {
+ if (branch.getBranchType() == BranchType.SHORT || branch.getBranchType() == BranchType.PULL_REQUEST) {
return ShortLivingBranchQualityGate.GATE;
}
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
-import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT;
-import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
-import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric;
-import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
-import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
-import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-import static org.sonar.server.ws.WsUtils.checkRequest;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY;
import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_COMPONENT;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_ID;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_KEY;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KEYS;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
+import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
+import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric;
+import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class ComponentAction implements MeasuresWsAction {
private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE);
.setInternal(true)
.setSince("6.6");
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
+
createMetricKeysParameter(action);
createAdditionalFieldsParameter(action);
createDeveloperParameters(action);
String componentKey = request.getComponent();
String componentId = request.getComponentId();
String branch = request.getBranch();
- checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH);
- return branch == null
- ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_COMPONENT)
- : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
+ String pullRequest = request.getPullRequest();
+ checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'",
+ DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_KEY);
+ }
+ checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
private Optional<ComponentDto> getReferenceComponent(DbSession dbSession, ComponentDto component) {
.setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID))
.setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
.setAdditionalFields(request.paramAsStrings(PARAM_ADDITIONAL_FIELDS))
.setMetricKeys(request.mandatoryParamAsStrings(PARAM_METRIC_KEYS));
checkRequest(!componentRequest.getMetricKeys().isEmpty(), "At least one metric key must be provided");
private String componentId;
private String component;
private String branch;
+ private String pullRequest;
private List<String> metricKeys;
private List<String> additionalFields;
private String developerId;
return this;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public ComponentRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
private List<String> getMetricKeys() {
return metricKeys;
}
.setName(component.name())
.setQualifier(component.qualifier());
Protobuf.setNullable(component.getBranch(), wsComponent::setBranch);
+ Protobuf.setNullable(component.getPullRequest(), wsComponent::setPullRequest);
Protobuf.setNullable(component.path(), wsComponent::setPath);
Protobuf.setNullable(component.description(), wsComponent::setDescription);
Protobuf.setNullable(component.language(), wsComponent::setLanguage);
import static org.sonar.db.component.ComponentTreeQuery.Strategy.CHILDREN;
import static org.sonar.db.component.ComponentTreeQuery.Strategy.LEAVES;
import static org.sonar.server.component.ComponentFinder.ParamNames.BASE_COMPONENT_ID_AND_KEY;
-import static org.sonar.server.component.ComponentFinder.ParamNames.DEVELOPER_ID_AND_KEY;
-import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
-import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.updateMeasureBuilder;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters;
-import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
-import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric;
-import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
-import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
-import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
-import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
-import static org.sonar.server.ws.WsUtils.checkRequest;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_COMPONENT_TREE;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS;
import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_STRATEGY;
+import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
+import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.updateMeasureBuilder;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters;
+import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
+import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric;
+import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
+import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
/**
* <p>Navigate through components based on different strategy with specified measures.
.setInternal(true)
.setSince("6.6");
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
+
action.createParam(PARAM_METRIC_SORT)
.setDescription(
format("Metric key to sort by. The '%s' parameter must contain the '%s' or '%s' value. It must be part of the '%s' parameter", Param.SORT, METRIC_SORT, METRIC_PERIOD_SORT,
.setBaseComponentId(request.param(DEPRECATED_PARAM_BASE_COMPONENT_ID))
.setComponent(request.param(PARAM_COMPONENT))
.setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
.setMetricKeys(metricKeys)
.setStrategy(request.mandatoryParam(PARAM_STRATEGY))
.setQualifiers(request.paramAsStrings(PARAM_QUALIFIERS))
}
private ComponentDto loadComponent(DbSession dbSession, ComponentTreeRequest request) {
- String componentKey = request.getComponent();
String componentId = request.getBaseComponentId();
+ String componentKey = request.getComponent();
String branch = request.getBranch();
- checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", DEPRECATED_PARAM_BASE_COMPONENT_ID, PARAM_BRANCH);
- return branch == null
- ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, BASE_COMPONENT_ID_AND_KEY)
- : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
+ String pullRequest = request.getPullRequest();
+ checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'",
+ DEPRECATED_PARAM_BASE_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, BASE_COMPONENT_ID_AND_KEY);
+ }
+ checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
private Map<String, ComponentDto> searchReferenceComponentsById(DbSession dbSession, List<ComponentDto> components) {
private String baseComponentId;
private String component;
private String branch;
+ private String pullRequest;
private String strategy;
private List<String> qualifiers;
private List<String> additionalFields;
return this;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public ComponentTreeRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
@CheckForNull
public String getStrategy() {
return strategy;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.Measures.SearchHistoryResponse;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
import static java.lang.String.format;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_SEARCH_HISTORY;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_FROM;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRICS;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_TO;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class SearchHistoryAction implements MeasuresWsAction {
this.userSession = userSession;
}
- private static SearchHistoryRequest toWsRequest(Request request) {
- return SearchHistoryRequest.builder()
- .setComponent(request.mandatoryParam(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH))
- .setMetrics(request.mandatoryParamAsStrings(PARAM_METRICS))
- .setFrom(request.param(PARAM_FROM))
- .setTo(request.param(PARAM_TO))
- .setPage(request.mandatoryParamAsInt(Param.PAGE))
- .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
- .build();
- }
-
@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_SEARCH_HISTORY)
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setSince("7.1")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
+
action.createParam(PARAM_METRICS)
.setDescription("Comma-separated list of metric keys")
.setRequired(true)
writeProtobuf(searchHistoryResponse, request, response);
}
+ private static SearchHistoryRequest toWsRequest(Request request) {
+ return SearchHistoryRequest.builder()
+ .setComponent(request.mandatoryParam(PARAM_COMPONENT))
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
+ .setMetrics(request.mandatoryParamAsStrings(PARAM_METRICS))
+ .setFrom(request.param(PARAM_FROM))
+ .setTo(request.param(PARAM_TO))
+ .setPage(request.mandatoryParamAsInt(Param.PAGE))
+ .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
+ .build();
+ }
+
private Function<SearchHistoryRequest, SearchHistoryResult> search() {
return request -> {
try (DbSession dbSession = dbClient.openSession(false)) {
private ComponentDto loadComponent(DbSession dbSession, SearchHistoryRequest request) {
String componentKey = request.getComponent();
String branch = request.getBranch();
- if (branch != null) {
- return componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
- }
- return componentFinder.getByKey(dbSession, componentKey);
+ String pullRequest = request.getPullRequest();
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
static class SearchHistoryRequest {
private final String component;
private final String branch;
+ private final String pullRequest;
private final List<String> metrics;
private final String from;
private final String to;
public SearchHistoryRequest(Builder builder) {
this.component = builder.component;
this.branch = builder.branch;
+ this.pullRequest = builder.pullRequest;
this.metrics = builder.metrics;
this.from = builder.from;
this.to = builder.to;
return branch;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
public List<String> getMetrics() {
return metrics;
}
static class Builder {
private String component;
private String branch;
+ private String pullRequest;
private List<String> metrics;
private String from;
private String to;
return this;
}
+ public Builder setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
public Builder setMetrics(List<String> metrics) {
this.metrics = metrics;
return this;
import org.sonar.server.badge.ws.ProjectBadgesWsModule;
import org.sonar.server.batch.BatchWsModule;
import org.sonar.server.branch.BranchFeatureProxyImpl;
+import org.sonar.server.branch.pr.ws.PullRequestWsModule;
import org.sonar.server.branch.ws.BranchWsModule;
import org.sonar.server.ce.ws.CeWsModule;
import org.sonar.server.component.ComponentCleanerService;
// components
BranchWsModule.class,
+ PullRequestWsModule.class,
ProjectsWsModule.class,
ProjectsEsModule.class,
ProjectTagsWsModule.class,
public static final String PARAM_FROM = "from";
public static final String PARAM_TO = "to";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
private ProjectAnalysesWsParameters() {
// static access only
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC;
-import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonar.server.projectanalysis.ws.EventCategory.OTHER;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_BRANCH;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_CATEGORY;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_FROM;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PROJECT;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_TO;
import static org.sonar.server.projectanalysis.ws.SearchRequest.DEFAULT_PAGE_SIZE;
+import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class SearchAction implements ProjectAnalysesWsAction {
private static final Set<String> ALLOWED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW);
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setSince("7.1")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
+
action.createParam(PARAM_CATEGORY)
.setDescription("Event category. Filter analyses that have at least one event of the category specified.")
.setPossibleValues(EnumSet.allOf(EventCategory.class))
return SearchRequest.builder()
.setProject(request.mandatoryParam(PARAM_PROJECT))
.setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST))
.setCategory(category == null ? null : EventCategory.valueOf(category))
.setPage(request.mandatoryParamAsInt(Param.PAGE))
.setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
private ComponentDto loadComponent(DbSession dbSession, SearchRequest request) {
String project = request.getProject();
String branch = request.getBranch();
- if (branch != null) {
- return componentFinder.getByKeyAndBranch(dbSession, project, branch);
- }
- return componentFinder.getByKey(dbSession, project);
+ String pullRequest = request.getPullRequest();
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, project, branch, pullRequest);
}
}
private final String project;
private final String branch;
+ private final String pullRequest;
private final EventCategory category;
private final int page;
private final int pageSize;
private SearchRequest(Builder builder) {
this.project = builder.project;
- this.branch= builder.branch;
+ this.branch = builder.branch;
+ this.pullRequest = builder.pullRequest;
this.category = builder.category;
this.page = builder.page;
this.pageSize = builder.pageSize;
return branch;
}
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
@CheckForNull
public EventCategory getCategory() {
return category;
public static class Builder {
private String project;
private String branch;
+ private String pullRequest;
private EventCategory category;
private int page = 1;
private int pageSize = DEFAULT_PAGE_SIZE;
return this;
}
+ public Builder setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
public Builder setCategory(@Nullable EventCategory category) {
this.category = category;
return this;
import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN;
/**
- * Offers constants describing the Hardcoded Quality Gate for short living branches.
+ * Offers constants describing the Hardcoded Quality Gate for short living branches and pull requests.
*/
public final class ShortLivingBranchQualityGate {
public static final long ID = -1_963_456_987L;
}
enum Status {
- OPEN, CONFIRMED, REOPENED, RESOLVED, CLOSED
+ OPEN,
+ CONFIRMED,
+ REOPENED,
+ RESOLVED_FP,
+ RESOLVED_WF,
+ RESOLVED_FIXED
}
+
}
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
}
}
- private static class ChangedIssueImpl implements ChangedIssue {
+ static class ChangedIssueImpl implements ChangedIssue {
private final String key;
private final QGChangeEventListener.Status status;
private final RuleType type;
private ChangedIssueImpl(DefaultIssue issue) {
this.key = issue.key();
- this.status = QGChangeEventListener.Status.valueOf(issue.getStatus());
+ this.status = statusOf(issue);
this.type = issue.type();
}
+ static QGChangeEventListener.Status statusOf(DefaultIssue issue) {
+ switch (issue.status()) {
+ case Issue.STATUS_OPEN:
+ return QGChangeEventListener.Status.OPEN;
+ case Issue.STATUS_CONFIRMED:
+ return QGChangeEventListener.Status.CONFIRMED;
+ case Issue.STATUS_REOPENED:
+ return QGChangeEventListener.Status.REOPENED;
+ case Issue.STATUS_RESOLVED:
+ return statusOfResolved(issue);
+ default:
+ throw new IllegalStateException("Unexpected status: " + issue.status());
+ }
+ }
+
+ private static QGChangeEventListener.Status statusOfResolved(DefaultIssue issue) {
+ String resolution = issue.resolution();
+ Objects.requireNonNull(resolution, "A resolved issue should have a resolution");
+ switch (resolution) {
+ case Issue.RESOLUTION_FALSE_POSITIVE:
+ return QGChangeEventListener.Status.RESOLVED_FP;
+ case Issue.RESOLUTION_WONT_FIX:
+ return QGChangeEventListener.Status.RESOLVED_WF;
+ case Issue.RESOLUTION_FIXED:
+ return QGChangeEventListener.Status.RESOLVED_FIXED;
+ default:
+ throw new IllegalStateException("Unexpected resolution for a resolved issue: " + resolution);
+ }
+ }
+
@Override
public String getKey() {
return key;
import java.util.List;
import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.PropertyFieldDefinition;
import static org.sonar.server.setting.ws.SettingsWs.SETTING_ON_BRANCHES;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT;
+import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
.setDescription("Component key")
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
settingsWsSupport.addBranchParam(action);
+ settingsWsSupport.addPullRequestParam(action);
}
@Override
private static ListDefinitionsRequest toWsRequest(Request request) {
return new ListDefinitionsRequest()
.setComponent(request.param(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH));
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST));
}
private static Optional<String> getQualifier(Optional<ComponentDto> component) {
if (componentKey == null) {
return Optional.empty();
}
- ComponentDto component = componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch());
+ ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest());
userSession.checkComponentPermission(USER, component);
return Optional.of(component);
}
private String branch;
private String component;
+ private String pullRequest;
- public ListDefinitionsRequest setBranch(String branch) {
+ public ListDefinitionsRequest setComponent(@Nullable String component) {
+ this.component = component;
+ return this;
+ }
+
+ @CheckForNull
+ public String getComponent() {
+ return component;
+ }
+
+ public ListDefinitionsRequest setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}
+ @CheckForNull
public String getBranch() {
return branch;
}
- public ListDefinitionsRequest setComponent(String component) {
- this.component = component;
+ public ListDefinitionsRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
return this;
}
- public String getComponent() {
- return component;
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
}
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.server.ws.Change;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEYS;
+import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
public class ResetAction implements SettingsWsAction {
.setExampleValue(KEY_BRANCH_EXAMPLE_001)
.setInternal(true)
.setSince("6.6");
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
}
@Override
private static ResetRequest toWsRequest(Request request) {
return new ResetRequest()
- .setKeys(request.paramAsStrings(PARAM_KEYS))
+ .setKeys(request.mandatoryParamAsStrings(PARAM_KEYS))
.setComponent(request.param(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH));
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST));
}
private Optional<ComponentDto> getComponent(DbSession dbSession, ResetRequest request) {
if (componentKey == null) {
return Optional.empty();
}
- return Optional.of(componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch()));
+ return Optional.of(componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest()));
}
private void checkPermissions(Optional<ComponentDto> component) {
private static class ResetRequest {
private String branch;
+ private String pullRequest;
private String component;
private List<String> keys;
- public ResetRequest setBranch(String branch) {
+ public ResetRequest setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}
+ @CheckForNull
public String getBranch() {
return branch;
}
- public ResetRequest setComponent(String component) {
+ public ResetRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public ResetRequest setComponent(@Nullable String component) {
this.component = component;
return this;
}
+ @CheckForNull
public String getComponent() {
return component;
}
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.PropertyType;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_FIELD_VALUES;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEY;
+import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUE;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_VALUES;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
.setDescription("Component key")
.setDeprecatedKey("componentKey", "6.3")
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
+
settingsWsSupport.addBranchParam(action);
+ settingsWsSupport.addPullRequestParam(action);
}
@Override
.setValues(request.multiParam(PARAM_VALUES))
.setFieldValues(request.multiParam(PARAM_FIELD_VALUES))
.setComponent(request.param(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH));
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST));
checkArgument(isNotEmpty(set.getKey()), "Setting key is mandatory and must not be empty");
checkArgument(set.getValues() != null, "Setting values must not be null");
checkArgument(set.getFieldValues() != null, "Setting fields values must not be null");
if (componentKey == null) {
return Optional.empty();
}
- return Optional.of(componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, request.getBranch()));
+ return Optional.of(componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, request.getBranch(), request.getPullRequest()));
}
private PropertyDto toProperty(SetRequest request, Optional<ComponentDto> component) {
private static class SetRequest {
private String branch;
+ private String pullRequest;
private String component;
private List<String> fieldValues;
private String key;
private String value;
private List<String> values;
- public SetRequest setBranch(String branch) {
+ public SetRequest setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}
+ @CheckForNull
public String getBranch() {
return branch;
}
- public SetRequest setComponent(String component) {
+ public SetRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public SetRequest setComponent(@Nullable String component) {
this.component = component;
return this;
}
+ @CheckForNull
public String getComponent() {
return component;
}
return key;
}
- public SetRequest setValue(String value) {
+ public SetRequest setValue(@Nullable String value) {
this.value = value;
return this;
}
+ @CheckForNull
public String getValue() {
return value;
}
- public SetRequest setValues(List<String> values) {
+ public SetRequest setValues(@Nullable List<String> values) {
this.values = values;
return this;
}
public static final String PARAM_COMPONENT = "component";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
public static final String PARAM_KEYS = "keys";
public static final String PARAM_KEY = "key";
public static final String PARAM_VALUE = "value";
import static org.sonar.api.web.UserRole.ADMIN;
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH;
+import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
@ServerSide
public class SettingsWsSupport {
.setInternal(true)
.setSince("6.6");
}
+
+ WebService.NewParam addPullRequestParam(WebService.NewAction action) {
+ return action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request. Only available on following settings : %s", SettingsWs.SETTING_ON_BRANCHES.stream().collect(COMMA_JOINER))
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
+ .setInternal(true)
+ .setSince("7.1");
+ }
}
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.server.ws.Change;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_BRANCH;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_COMPONENT;
import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_KEYS;
+import static org.sonar.server.setting.ws.SettingsWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
-import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
action.createParam(PARAM_COMPONENT)
.setDescription("Component key")
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
- action.createParam(PARAM_BRANCH)
- .setDescription("Branch key")
- .setExampleValue(KEY_BRANCH_EXAMPLE_001)
- .setInternal(true)
- .setSince("6.6");
+ settingsWsSupport.addBranchParam(action);
+ settingsWsSupport.addPullRequestParam(action);
}
@Override
private static ValuesRequest toWsRequest(Request request) {
ValuesRequest result = new ValuesRequest()
.setComponent(request.param(PARAM_COMPONENT))
- .setBranch(request.param(PARAM_BRANCH));
+ .setBranch(request.param(PARAM_BRANCH))
+ .setPullRequest(request.param(PARAM_PULL_REQUEST));
if (request.hasParam(PARAM_KEYS)) {
result.setKeys(request.paramAsStrings(PARAM_KEYS));
}
if (componentKey == null) {
return Optional.empty();
}
- ComponentDto component = componentFinder.getByKeyAndOptionalBranch(dbSession, componentKey, valuesRequest.getBranch());
+ ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, valuesRequest.getBranch(), valuesRequest.getPullRequest());
if (!userSession.hasComponentPermission(USER, component) &&
- !userSession.hasComponentPermission(SCAN_EXECUTION, component) &&
- !userSession.hasPermission(OrganizationPermission.SCAN, component.getOrganizationUuid())) {
- throw insufficientPrivilegesException();
+ !userSession.hasComponentPermission(SCAN_EXECUTION, component) &&
+ !userSession.hasPermission(OrganizationPermission.SCAN, component.getOrganizationUuid())) {
+ throw insufficientPrivilegesException();
}
return Optional.of(component);
}
private static class ValuesRequest {
private String branch;
+ private String pullRequest;
private String component;
private List<String> keys;
- public ValuesRequest setBranch(String branch) {
+ public ValuesRequest setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}
+ @CheckForNull
public String getBranch() {
return branch;
}
- public ValuesRequest setComponent(String component) {
+ public ValuesRequest setPullRequest(@Nullable String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
+ public ValuesRequest setComponent(@Nullable String component) {
this.component = component;
return this;
}
+ @CheckForNull
public String getComponent() {
return component;
}
- public ValuesRequest setKeys(List<String> keys) {
+ public ValuesRequest setKeys(@Nullable List<String> keys) {
this.keys = keys;
return this;
}
+ @CheckForNull
public List<String> getKeys() {
return keys;
}
import static org.sonar.server.component.ComponentFinder.ParamNames.UUID_AND_KEY;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_FILE_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
+import static org.sonar.server.ws.WsUtils.checkRequest;
public class LinesAction implements SourcesWsAction {
private static final String PARAM_FROM = "from";
private static final String PARAM_TO = "to";
private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private final ComponentFinder componentFinder;
private final SourceService sourceService;
"has been renamed \"lineHits\", \"conditions\" and \"coveredConditions\""),
new Change("6.2", "fields \"itLineHits\", \"itConditions\" and \"itCoveredConditions\" " +
"are no more returned"),
- new Change("6.6", "fields \"branch\" added"))
+ new Change("6.6", "field \"branch\" added"))
.setHandler(this);
action
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
+
action
.createParam(PARAM_FROM)
.setDescription("First line to return. Starts from 1")
String componentKey = wsRequest.param(PARAM_KEY);
String componentId = wsRequest.param(PARAM_UUID);
String branch = wsRequest.param(PARAM_BRANCH);
- checkArgument(componentId == null || branch == null, "'%s' and '%s' parameters cannot be used at the same time", PARAM_UUID,
- PARAM_BRANCH);
- return branch == null
- ? componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, UUID_AND_KEY)
- : componentFinder.getByKeyAndBranch(dbSession, componentKey, branch);
+ String pullRequest = wsRequest.param(PARAM_PULL_REQUEST);
+ checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'",
+ PARAM_UUID, PARAM_BRANCH, PARAM_PULL_REQUEST);
+ if (branch == null && pullRequest == null) {
+ return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, UUID_AND_KEY);
+ }
+
+ checkRequest(componentKey!=null, "The '%s' parameter is missing", PARAM_KEY);
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
}
private void writeSource(Iterable<DbFileSources.Line> lines, JsonWriter json) {
import org.sonar.server.user.UserSession;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
public class RawAction implements SourcesWsAction {
+ private static final String PARAM_KEY = "key";
+ private static final String PARAM_BRANCH = "branch";
+ private static final String PARAM_PULL_REQUEST = "pullRequest";
private final DbClient dbClient;
private final SourceService sourceService;
private final UserSession userSession;
.setHandler(this);
action
- .createParam("key")
+ .createParam(PARAM_KEY)
.setRequired(true)
.setDescription("File key")
.setExampleValue("my_project:src/foo/Bar.php");
action
- .createParam("branch")
+ .createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ action
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
public void handle(Request request, Response response) {
- String fileKey = request.mandatoryParam("key");
- String branch = request.param("branch");
+ String fileKey = request.mandatoryParam(PARAM_KEY);
+ String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
try (DbSession dbSession = dbClient.openSession(false)) {
- ComponentDto file = componentFinder.getByKeyAndOptionalBranch(dbSession, fileKey, branch);
+ ComponentDto file = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, fileKey, branch, pullRequest);
userSession.checkComponentPermission(UserRole.CODEVIEWER, file);
Optional<Iterable<String>> lines = sourceService.getLinesAsRawText(dbSession, file.uuid(), 1, Integer.MAX_VALUE);
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
public class ListAction implements TestsWsAction {
public static final String SOURCE_FILE_KEY = "sourceFileKey";
public static final String SOURCE_FILE_LINE_NUMBER = "sourceFileLineNumber";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
private final DbClient dbClient;
private final TestIndex testIndex;
.setDeprecatedSince("5.6")
.setHandler(this)
.setChangelog(new Change("6.6", "\"fileBranch\" field is now returned"))
+ .setChangelog(new Change("7.1", "\"filePullRequest\" field is now returned"))
.addPagingParams(100, MAX_LIMIT);
action
.setSince("6.6")
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ action.createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setSince("7.1")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
String sourceFileUuid = request.param(SOURCE_FILE_ID);
String sourceFileKey = request.param(SOURCE_FILE_KEY);
String branch = request.param(PARAM_BRANCH);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
Integer sourceFileLineNumber = request.paramAsInt(SOURCE_FILE_LINE_NUMBER);
SearchOptions searchOptions = new SearchOptions().setPage(
request.mandatoryParamAsInt(PAGE),
SearchResult<TestDoc> tests;
Map<String, ComponentDto> componentsByTestFileUuid;
try (DbSession dbSession = dbClient.openSession(false)) {
- tests = searchTests(dbSession, testUuid, testFileUuid, testFileKey, sourceFileUuid, sourceFileKey, branch, sourceFileLineNumber, searchOptions);
+ tests = searchTests(dbSession, testUuid, testFileUuid, testFileKey, sourceFileUuid, sourceFileKey, branch, pullRequest, sourceFileLineNumber, searchOptions);
componentsByTestFileUuid = buildComponentsByTestFileUuid(dbSession, tests.getDocs());
}
testBuilder.setFileKey(component.getKey());
testBuilder.setFileName(component.longName());
setNullable(component.getBranch(), testBuilder::setFileBranch);
+ setNullable(component.getPullRequest(), testBuilder::setFilePullRequest);
}
testBuilder.setStatus(Tests.TestStatus.valueOf(testDoc.status()));
if (testDoc.durationInMs() != null) {
}
private SearchResult<TestDoc> searchTests(DbSession dbSession, @Nullable String testUuid, @Nullable String testFileUuid, @Nullable String testFileKey,
- @Nullable String sourceFileUuid, @Nullable String sourceFileKey, @Nullable String branch, @Nullable Integer sourceFileLineNumber, SearchOptions searchOptions) {
+ @Nullable String sourceFileUuid, @Nullable String sourceFileKey, @Nullable String branch, @Nullable String pullRequest,
+ @Nullable Integer sourceFileLineNumber, SearchOptions searchOptions) {
if (testUuid != null) {
TestDoc testDoc = checkFoundWithOptional(testIndex.getNullableByTestUuid(testUuid), "Test with id '%s' is not found", testUuid);
checkComponentUuidPermission(dbSession, testDoc.fileUuid());
return testIndex.searchByTestFileUuid(testFileUuid, searchOptions);
}
if (testFileKey != null) {
- ComponentDto testFile = componentFinder.getByKeyAndOptionalBranch(dbSession, testFileKey, branch);
+ ComponentDto testFile = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, testFileKey, branch, pullRequest);
userSession.checkComponentPermission(CODEVIEWER, testFile);
return testIndex.searchByTestFileUuid(testFile.uuid(), searchOptions);
}
return testIndex.searchBySourceFileUuidAndLineNumber(sourceFile.uuid(), sourceFileLineNumber, searchOptions);
}
if (sourceFileKey != null && sourceFileLineNumber != null) {
- ComponentDto sourceFile = componentFinder.getByKeyAndOptionalBranch(dbSession, sourceFileKey, branch);
+ ComponentDto sourceFile = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, sourceFileKey, branch, pullRequest);
userSession.checkComponentPermission(CODEVIEWER, sourceFile);
return testIndex.searchBySourceFileUuidAndLineNumber(sourceFile.uuid(), sourceFileLineNumber, searchOptions);
}
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
public class ComponentAction implements NavigationWsAction {
static final String PARAM_COMPONENT = "component";
static final String PARAM_BRANCH = "branch";
+ static final String PARAM_PULL_REQUEST = "pullRequest";
private static final String PROPERTY_CONFIGURABLE = "configurable";
private static final String PROPERTY_HAS_ROLE_POLICY = "hasRolePolicy";
.setDescription("Branch key")
.setInternal(true)
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
+
+ projectNavigation
+ .createParam(PARAM_PULL_REQUEST)
+ .setDescription("Pull request id")
+ .setInternal(true)
+ .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
}
@Override
String componentKey = request.mandatoryParam(PARAM_COMPONENT);
try (DbSession session = dbClient.openSession(false)) {
String branch = request.param(PARAM_BRANCH);
- ComponentDto component = componentFinder.getByKeyAndOptionalBranch(session, componentKey, branch);
+ String pullRequest = request.param(PARAM_PULL_REQUEST);
+ ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(session, componentKey, branch, pullRequest);
if (!userSession.hasComponentPermission(USER, component) &&
!userSession.hasComponentPermission(ADMIN, component) &&
!userSession.isSystemAdministrator()) {
new Change("6.3", "The password is only mandatory when creating local users, and should not be set on non local users"),
new Change("6.3", "The 'infos' message is no more returned when a user is reactivated"))
.setPost(true)
+ .setResponseExample(getClass().getResource("create-example.json"))
.setHandler(this);
action.createParam(PARAM_LOGIN)
.setExampleValue("myname@email.com");
action.createParam(PARAM_SCM_ACCOUNTS)
- .setDescription("This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT)
+ .setDescription("Comma-separated list of SCM accounts. This parameter is deprecated, please use '%s' instead", PARAM_SCM_ACCOUNT)
.setDeprecatedKey(PARAM_SCM_ACCOUNTS_DEPRECATED, "6.0")
.setDeprecatedSince("6.1")
.setExampleValue("myscmaccount1,myscmaccount2");
action.createParam(PARAM_SCM_ACCOUNT)
- .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.")
+ .setDescription("List of SCM accounts. To set several values, the parameter must be called once for each value.")
.setExampleValue("scmAccount=firstValue&scmAccount=secondValue&scmAccount=thirdValue");
action.createParam(PARAM_LOCAL)
switch (type) {
case PROJECT:
checkArgument(isNotBlank(componentParameter), PARAMETER_REQUIRED, type.name(), PARAM_COMPONENT);
- return componentFinder.getByKeyAndOptionalBranch(dbSession, componentParameter, branchParameter).uuid();
+ return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentParameter, branchParameter, null).uuid();
case PORTFOLIO:
case APPLICATION:
checkArgument(isNotBlank(componentParameter), PARAMETER_REQUIRED, type.name(), PARAM_COMPONENT);
}
public enum Type {
- LONG, SHORT
+ LONG, SHORT, PULL_REQUEST
}
@Override
}
return format("%s/dashboard?branch=%s&id=%s",
server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
- } else {
+ }
+ if (branch.getType() == Branch.Type.SHORT) {
return format("%s/project/issues?branch=%s&id=%s&resolved=false",
server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
}
+ if (branch.getType() == Branch.Type.PULL_REQUEST) {
+ return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false",
+ server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
+ }
+ return projectUrlOf(project);
}
private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) {
public static final String KEY_ORG_EXAMPLE_002 = "foo-company";
public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch";
+ public static final String KEY_PULL_REQUEST_EXAMPLE_001 = "5461";
public static final String NAME_WEBHOOK_EXAMPLE_001 = "My Webhook";
public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar";
--- /dev/null
+{
+ "pullRequests": [
+ {
+ "key": "123",
+ "title": "Add feature X",
+ "branch": "feature/bar",
+ "base": "feature/foo",
+ "status": {
+ "bugs": 0,
+ "vulnerabilities": 0,
+ "codeSmells": 0
+ },
+ "analysisDate": "2017-04-01T02:15:42+0200",
+ "url": "https://github.com/SonarSource/sonar-core-plugins/pull/32"
+ }
+ ]
+}
--- /dev/null
+{
+ "user": {
+ "login": "ada.lovelace",
+ "name": "Ada Lovelace",
+ "email": "ada.lovelace@noteg.com",
+ "scmAccounts": ["ada.lovelace"],
+ "active": true,
+ "local": true
+ }
+}
.containsExactlyInAnyOrder(
tuple("project", true),
tuple("branch", false),
+ tuple("pullRequest", false),
tuple("metric", true));
}
.extracting(Param::key, Param::isRequired)
.containsExactlyInAnyOrder(
tuple("project", true),
- tuple("branch", false));
+ tuple("branch", false),
+ tuple("pullRequest", false));
}
private MetricDto createQualityGateMetric() {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.branch.pr.ws;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+
+public class DeleteActionTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);
+ private ComponentFinder componentFinder = TestComponentFinder.from(db);
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ public WsActionTester ws = new WsActionTester(new DeleteAction(db.getDbClient(), componentFinder, userSession, componentCleanerService));
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("delete");
+ assertThat(definition.isPost()).isTrue();
+ assertThat(definition.isInternal()).isFalse();
+ assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project", "pullRequest");
+ assertThat(definition.since()).isEqualTo("7.1");
+ }
+
+ @Test
+ public void delete_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("1984").setBranchType(PULL_REQUEST));
+
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("pullRequest", "1984")
+ .execute();
+ verifyDeletedKey(branch.getDbKey());
+ }
+
+ @Test
+ public void fail_if_missing_project_parameter() {
+ userSession.logIn();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("The 'project' parameter is missing");
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void fail_if_missing_pull_request_parameter() {
+ userSession.logIn();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("The 'pullRequest' parameter is missing");
+
+ ws.newRequest().setParam("project", "projectName").execute();
+ }
+
+ @Test
+ public void fail_if_not_logged_in() {
+ expectedException.expect(UnauthorizedException.class);
+ expectedException.expectMessage("Authentication is required");
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void fail_if_pull_request_does_not_exist() {
+ ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("orwell"));
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Pull request '1984' is not found for project 'orwell'");
+
+ ws.newRequest()
+ .setParam("project", project.getDbKey())
+ .setParam("pullRequest", "1984")
+ .execute();
+ }
+
+ @Test
+ public void fail_if_project_does_not_exist() {
+ userSession.logIn();
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Project key 'foo' not found");
+
+ ws.newRequest()
+ .setParam("project", "foo")
+ .setParam("pullRequest", "123")
+ .execute();
+ }
+
+ private void verifyDeletedKey(String key) {
+ ArgumentCaptor<ComponentDto> argument = ArgumentCaptor.forClass(ComponentDto.class);
+ verify(componentCleanerService).deleteBranch(any(DbSession.class), argument.capture());
+ assertThat(argument.getValue().getDbKey()).isEqualTo(key);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.branch.pr.ws;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.ResourceTypesRule;
+import org.sonar.db.component.SnapshotTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.protobuf.DbProjectBranches;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueIteratorFactory;
+import org.sonar.server.permission.index.AuthorizationTypeSupport;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.ProjectPullRequests.ListWsResponse;
+import org.sonarqube.ws.ProjectPullRequests.PullRequest;
+
+import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+import static org.sonar.api.utils.DateUtils.dateToLong;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.db.component.BranchType.LONG;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.ProjectPullRequests.Status;
+
+public class ListActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ @Rule
+ public EsTester es = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);
+ private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
+ private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession));
+ private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer);
+
+ public WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), userSession, new ComponentFinder(db.getDbClient(), resourceTypes), issueIndex));
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+ assertThat(definition.key()).isEqualTo("list");
+ assertThat(definition.isPost()).isFalse();
+ assertThat(definition.isInternal()).isFalse();
+ assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("project");
+ assertThat(definition.since()).isEqualTo("7.1");
+ }
+
+ @Test
+ public void json_example() {
+ ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("sonarqube"));
+ ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo").setBranchType(LONG));
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setKey("123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(longLivingBranch.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch("feature/bar")
+ .setTitle("Add feature X")
+ .setUrl("https://github.com/SonarSource/sonar-core-plugins/pull/32")
+ .build()));
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+ db.getDbClient().snapshotDao().insert(db.getSession(),
+ SnapshotTesting.newAnalysis(pullRequest).setLast(true).setCreatedAt(DateUtils.parseDateTime("2017-04-01T01:15:42+0100").getTime()));
+ db.commit();
+
+ String json = ws.newRequest()
+ .setParam("project", project.getDbKey())
+ .execute()
+ .getInput();
+
+ assertJson(json).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ db.components().insertProjectBranch(project,
+ b -> b.setKey("123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(project.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getDbKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList())
+ .extracting(PullRequest::getKey, PullRequest::getBranch, PullRequest::getIsOrphan)
+ .containsExactlyInAnyOrder(tuple("123", "feature/bar", false));
+ }
+
+ @Test
+ public void project_with_zero_branches() {
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+ String json = ws.newRequest()
+ .setParam("project", project.getDbKey())
+ .setMediaType(MediaTypes.JSON)
+ .execute()
+ .getInput();
+
+ assertJson(json).isSimilarTo("{\"pullRequests\": []}");
+ }
+
+ @Test
+ public void pull_requests() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto longLivingBranch = db.components().insertProjectBranch(project,
+ b -> b.setKey("long").setBranchType(BranchType.LONG));
+ ComponentDto pullRequestOnLong = db.components().insertProjectBranch(project,
+ b -> b.setKey("pull_request_on_long")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(longLivingBranch.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+ ComponentDto pullRequestOnMaster = db.components().insertProjectBranch(project,
+ b -> b.setKey("pull_request_on_master")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(project.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList())
+ .extracting(PullRequest::getKey, PullRequest::getBase)
+ .containsExactlyInAnyOrder(
+ tuple(pullRequestOnLong.getPullRequest(), longLivingBranch.getBranch()),
+ tuple(pullRequestOnMaster.getPullRequest(), "master"));
+ }
+
+ @Test
+ public void base_branch_is_using_default_main_name_when_main_branch_has_no_name() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setKey("pr-123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(project.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder()
+ .setBranch("feature123").build()));
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", pullRequest.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequests(0))
+ .extracting(PullRequest::getKey, PullRequest::getBase)
+ .containsExactlyInAnyOrder(pullRequest.getPullRequest(), "master");
+ }
+
+ @Test
+ public void pull_request_on_removed_branch() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setKey("pr-123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid("unknown")
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList())
+ .extracting(PullRequest::getKey, PullRequest::hasBase, PullRequest::getIsOrphan)
+ .containsExactlyInAnyOrder(
+ tuple(pullRequest.getPullRequest(), false, true));
+ }
+
+ @Test
+ public void status_on_pull_requests() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setKey("pr-123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(longLivingBranch.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+ RuleDefinitionDto rule = db.rules().insert();
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(BUG).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(BUG).setResolution(RESOLUTION_FIXED));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(VULNERABILITY).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(VULNERABILITY).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(null));
+ db.issues().insert(rule, pullRequest, pullRequest, i -> i.setType(CODE_SMELL).setResolution(RESOLUTION_FALSE_POSITIVE));
+ issueIndexer.indexOnStartup(emptySet());
+ permissionIndexerTester.allowOnlyAnyone(project);
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList().stream().map(PullRequest::getStatus))
+ .extracting(Status::hasBugs, Status::getBugs, Status::hasVulnerabilities, Status::getVulnerabilities, Status::hasCodeSmells, Status::getCodeSmells)
+ .containsExactlyInAnyOrder(tuple(true, 1L, true, 2L, true, 3L));
+ }
+
+ @Test
+ public void status_on_pull_request_with_no_issue() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+ db.components().insertProjectBranch(project,
+ b -> b.setKey("pr-123")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(longLivingBranch.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/bar").build()));
+ issueIndexer.indexOnStartup(emptySet());
+ permissionIndexerTester.allowOnlyAnyone(project);
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList().stream().map(PullRequest::getStatus))
+ .extracting(Status::getBugs, Status::getVulnerabilities, Status::getCodeSmells)
+ .containsExactlyInAnyOrder(tuple(0L, 0L, 0L));
+ }
+
+ @Test
+ public void response_contains_date_of_last_analysis() {
+ Long lastAnalysisLongLivingBranch = dateToLong(parseDateTime("2017-04-01T00:00:00+0100"));
+ Long previousAnalysisPullRequest = dateToLong(parseDateTime("2017-04-02T00:00:00+0100"));
+ Long lastAnalysisPullRequest = dateToLong(parseDateTime("2017-04-03T00:00:00+0100"));
+
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+ ComponentDto pullRequest1 = db.components().insertProjectBranch(project,
+ b -> b.setKey("pr1")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(project.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/pr1").build()));
+
+ ComponentDto longLivingBranch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.LONG));
+
+ ComponentDto pullRequest2 = db.components().insertProjectBranch(project,
+ b -> b.setKey("pr2")
+ .setBranchType(PULL_REQUEST)
+ .setMergeBranchUuid(longLivingBranch2.uuid())
+ .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch("feature/pr2").build()));
+
+ db.getDbClient().snapshotDao().insert(db.getSession(),
+ SnapshotTesting.newAnalysis(longLivingBranch2).setCreatedAt(lastAnalysisLongLivingBranch));
+ db.getDbClient().snapshotDao().insert(db.getSession(),
+ SnapshotTesting.newAnalysis(pullRequest2).setCreatedAt(previousAnalysisPullRequest).setLast(false));
+ db.getDbClient().snapshotDao().insert(db.getSession(),
+ SnapshotTesting.newAnalysis(pullRequest2).setCreatedAt(lastAnalysisPullRequest));
+ db.commit();
+ issueIndexer.indexOnStartup(emptySet());
+ permissionIndexerTester.allowOnlyAnyone(project);
+
+ ListWsResponse response = ws.newRequest()
+ .setParam("project", project.getKey())
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getPullRequestsList())
+ .extracting(PullRequest::hasAnalysisDate, b -> "".equals(b.getAnalysisDate()) ? null : dateToLong(parseDateTime(b.getAnalysisDate())))
+ .containsExactlyInAnyOrder(
+ tuple(false, null),
+ tuple(true, lastAnalysisPullRequest));
+ }
+
+ @Test
+ public void fail_when_using_branch_db_key() throws Exception {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertMainBranch(organization);
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+ ComponentDto branch = db.components().insertProjectBranch(project);
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey()));
+
+ ws.newRequest()
+ .setParam("project", branch.getDbKey())
+ .execute();
+ }
+
+ @Test
+ public void fail_if_missing_project_parameter() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("The 'project' parameter is missing");
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void fail_if_not_a_reference_on_project() {
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+ userSession.logIn().addProjectPermission(UserRole.USER, project);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Invalid project key");
+
+ ws.newRequest()
+ .setParam("project", file.getDbKey())
+ .execute();
+ }
+
+ @Test
+ public void fail_if_project_does_not_exist() {
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Component key 'foo' not found");
+
+ ws.newRequest()
+ .setParam("project", "foo")
+ .execute();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.branch.pr.ws;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class PullRequestWsModuleTest {
+ @Test
+ public void verify_count_of_added_components() {
+ ComponentContainer container = new ComponentContainer();
+ new PullRequestWsModule().configure(container);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.branch.pr.ws;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.TestUtils.hasOnlyPrivateConstructors;
+
+public class PullRequestsWsParametersTest {
+
+ @Test
+ public void private_method() {
+ assertThat(hasOnlyPrivateConstructors(PullRequestsWsParameters.class)).isTrue();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.branch.pr.ws;
+
+import org.junit.Test;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PullRequestsWsTest {
+
+ @Test
+ public void define_ws() {
+ PullRequestsWs underTest = new PullRequestsWs(new PullRequestWsAction() {
+ @Override
+ public void define(WebService.NewController context) {
+ context.createAction("foo").setHandler(this);
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+
+ }
+ });
+
+ WebService.Context context = new WebService.Context();
+ underTest.define(context);
+
+ assertThat(context.controller("api/project_pull_requests").action("foo")).isNotNull();
+ }
+
+}
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
import static org.sonar.test.JsonAssert.assertJson;
-import static org.sonarqube.ws.ProjectBranches.Branch.Status;
+import static org.sonarqube.ws.ProjectBranches.Status;
public class ListActionTest {
*/
package org.sonar.server.ce.ws;
-import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.sonar.db.ce.CeQueueDto;
import org.sonar.db.ce.CeTaskCharacteristicDto;
import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.ws.WsActionTester;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.Ce;
-import org.sonarqube.ws.Common;
-import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Ce.ActivityResponse;
import org.sonarqube.ws.Ce.Task;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.MediaTypes;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_KEY;
import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_TYPE_KEY;
+import static org.sonar.db.ce.CeTaskCharacteristicDto.PULL_REQUEST;
import static org.sonar.db.component.BranchType.LONG;
import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_ID;
import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_QUERY;
tuple("T2", branch, Common.BranchType.LONG, Ce.TaskStatus.PENDING));
}
+ @Test
+ public void pull_request_in_past_activity() {
+ logInAsSystemAdministrator();
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
+ SnapshotDto analysis = db.components().insertSnapshot(pullRequest);
+ CeActivityDto activity = insertActivity("T1", project, SUCCESS, analysis);
+ insertCharacteristic(activity, PULL_REQUEST, pullRequest.getPullRequest());
+
+ ActivityResponse response = ws.newRequest().executeProtobuf(ActivityResponse.class);
+
+ assertThat(response.getTasksList())
+ .extracting(Task::getId, Ce.Task::getPullRequest, Ce.Task::hasPullRequestTitle, Ce.Task::getStatus, Ce.Task::getComponentKey)
+ .containsExactlyInAnyOrder(
+ // TODO the pull request title must be loaded from db
+ tuple("T1", pullRequest.getPullRequest(), false, Ce.TaskStatus.SUCCESS, pullRequest.getKey()));
+ }
+
+ @Test
+ public void pull_request_in_queue_analysis() {
+ logInAsSystemAdministrator();
+ String branch = "pr-123";
+ CeQueueDto queue1 = insertQueue("T1", null, IN_PROGRESS);
+ insertCharacteristic(queue1, PULL_REQUEST, branch);
+ CeQueueDto queue2 = insertQueue("T2", null, PENDING);
+ insertCharacteristic(queue2, PULL_REQUEST, branch);
+
+ ActivityResponse response = ws.newRequest()
+ .setParam("status", "FAILED,IN_PROGRESS,PENDING")
+ .executeProtobuf(ActivityResponse.class);
+
+ assertThat(response.getTasksList())
+ .extracting(Task::getId, Ce.Task::getPullRequest, Ce.Task::hasPullRequestTitle, Ce.Task::getStatus)
+ .containsExactlyInAnyOrder(
+ tuple("T1", branch, false, Ce.TaskStatus.IN_PROGRESS),
+ tuple("T2", branch, false, Ce.TaskStatus.PENDING));
+ }
+
@Test
public void fail_if_both_filters_on_component_id_and_name() {
expectedException.expect(BadRequestException.class);
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
assertThat(underTest.getByKeyAndBranch(dbSession, directory.getKey(), "my_branch").uuid()).isEqualTo(directory.uuid());
}
+ @Test
+ public void get_by_key_and_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST).setMergeBranchUuid(project.uuid()));
+ ComponentDto module = db.components().insertComponent(newModuleDto(branch));
+ ComponentDto directory = db.components().insertComponent(newDirectory(module, "scr"));
+ ComponentDto file = db.components().insertComponent(newFileDto(module));
+
+ assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, project.getKey(), null, "pr-123").uuid()).isEqualTo(branch.uuid());
+ assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, module.getKey(), null, "pr-123").uuid()).isEqualTo(module.uuid());
+ assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, file.getKey(), null, "pr-123").uuid()).isEqualTo(file.uuid());
+ assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, directory.getKey(), null, "pr-123").uuid()).isEqualTo(directory.uuid());
+ }
+
+ @Test
+ public void fail_when_pull_request_branch_provided() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Either branch or pull request can be provided, not both");
+
+ assertThat(underTest.getByKeyAndOptionalBranchOrPullRequest(dbSession, project.getKey(), "pr-123", "pr-123").uuid()).isEqualTo(pullRequest.uuid());
+ }
+
@Test
public void get_by_key_and_branch_accept_main_branch() {
ComponentDto project = db.components().insertMainBranch();
import static org.sonar.api.measures.CoreMetrics.TESTS_KEY;
import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
ComponentDto file = db.components().insertComponent(newFileDto(branch));
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time");
+ expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'");
ws.newRequest()
.setParam("uuid", file.uuid())
.execute();
}
+ @Test
+ public void fail_if_both_componentId_and_pull_request_parameters_provided() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto file = db.components().insertComponent(newFileDto(branch));
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'");
+
+ ws.newRequest()
+ .setParam("uuid", file.uuid())
+ .setParam("pullRequest", file.getPullRequest())
+ .execute();
+ }
+
@Test
public void fail_when_component_not_found() {
ComponentDto project = db.components().insertPrivateProject();
assertThat(action.isInternal()).isTrue();
assertThat(action.isPost()).isFalse();
assertThat(action.handler()).isNotNull();
- assertThat(action.params()).hasSize(3);
+ assertThat(action.params()).hasSize(4);
}
}
*/
package org.sonar.server.component.ws;
-import java.io.IOException;
import java.util.Date;
import javax.annotation.Nullable;
import org.junit.Rule;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST;
public class ShowActionTest {
@Rule
tuple("6.5", "Leak period date is added to the response"),
tuple("6.6", "'branch' is added to the response"),
tuple("6.6", "'version' is added to the response"));
- assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("component", "componentId", "branch");
+ assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "pullRequest");
WebService.Param componentId = action.param(PARAM_COMPONENT_ID);
assertThat(componentId.isRequired()).isFalse();
assertThat(branch.isInternal()).isTrue();
assertThat(branch.isRequired()).isFalse();
assertThat(branch.since()).isEqualTo("6.6");
+
+ WebService.Param pullRequest = action.param(PARAM_PULL_REQUEST);
+ assertThat(pullRequest.isInternal()).isTrue();
+ assertThat(pullRequest.isRequired()).isFalse();
+ assertThat(pullRequest.since()).isEqualTo("7.1");
}
@Test
tuple(branch.getKey(), branchKey, "1.1"));
}
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.addProjectPermission(UserRole.USER, project);
+ String pullRequest = "pr-1234";
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(pullRequest).setBranchType(PULL_REQUEST));
+ ComponentDto module = db.components().insertComponent(newModuleDto(branch));
+ ComponentDto directory = db.components().insertComponent(newDirectory(module, "dir"));
+ ComponentDto file = db.components().insertComponent(newFileDto(directory));
+ db.components().insertSnapshot(branch, s -> s.setVersion("1.1"));
+
+ ShowWsResponse response = ws.newRequest()
+ .setParam(PARAM_COMPONENT, file.getKey())
+ .setParam(PARAM_PULL_REQUEST, pullRequest)
+ .executeProtobuf(ShowWsResponse.class);
+
+ assertThat(response.getComponent())
+ .extracting(Component::getKey, Component::getPullRequest, Component::getVersion)
+ .containsExactlyInAnyOrder(file.getKey(), pullRequest, "1.1");
+ assertThat(response.getAncestorsList()).extracting(Component::getKey, Component::getPullRequest, Component::getVersion)
+ .containsExactlyInAnyOrder(
+ tuple(directory.getKey(), pullRequest, "1.1"),
+ tuple(module.getKey(), pullRequest, "1.1"),
+ tuple(branch.getKey(), pullRequest, "1.1"));
+ }
+
@Test
public void throw_ForbiddenException_if_user_doesnt_have_browse_permission_on_project() {
userSession.logIn();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time");
+ expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'");
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, branch.uuid())
import static org.sonar.api.resources.Qualifiers.FILE;
import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newChildComponent;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY;
assertThat(action.responseExample()).isNotNull();
assertThat(action.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder(
tuple("6.4", "The field 'id' is deprecated in the response"));
- assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "qualifiers", "strategy",
+ assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "componentId", "branch", "pullRequest", "qualifiers", "strategy",
"q", "s", "p", "asc", "ps");
Param componentId = action.param(PARAM_COMPONENT_ID);
tuple(file.getKey(), branchKey));
}
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.addProjectPermission(UserRole.USER, project);
+ String pullRequestId = "pr-123";
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(pullRequestId).setBranchType(PULL_REQUEST));
+ ComponentDto module = db.components().insertComponent(newModuleDto(branch));
+ ComponentDto directory = db.components().insertComponent(newDirectory(module, "dir"));
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(directory));
+
+ TreeWsResponse response = ws.newRequest()
+ .setParam(PARAM_COMPONENT, module.getKey())
+ .setParam(PARAM_PULL_REQUEST, pullRequestId)
+ .executeProtobuf(TreeWsResponse.class);
+
+ assertThat(response.getBaseComponent()).extracting(Components.Component::getKey, Components.Component::getPullRequest)
+ .containsExactlyInAnyOrder(module.getKey(), pullRequestId);
+ assertThat(response.getComponentsList()).extracting(Components.Component::getKey, Components.Component::getPullRequest)
+ .containsExactlyInAnyOrder(
+ tuple(directory.getKey(), pullRequestId),
+ tuple(file.getKey(), pullRequestId));
+ }
+
@Test
public void fail_when_using_branch_db_key() {
ComponentDto project = db.components().insertMainBranch();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time");
+ expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'");
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, branch.uuid())
underTest.setBranch(new DefaultBranchImpl("master"));
}
+ @Test
+ public void setPullRequestId() {
+ AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl();
+
+ String pullRequestId = "pr-123";
+ underTest.setPullRequestId(pullRequestId);
+
+ assertThat(underTest.getPullRequestId()).isEqualTo(pullRequestId);
+ }
+
+ @Test
+ public void getPullRequestId_throws_ISE_when_holder_is_not_initialized() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Pull request id has not been set");
+
+ new AnalysisMetadataHolderImpl().getPullRequestId();
+ }
+
+ @Test
+ public void setPullRequestId_throws_ISE_when_called_twice() {
+ AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl();
+ underTest.setPullRequestId("pr-123");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Pull request id has already been set");
+ underTest.setPullRequestId("pr-234");
+ }
+
@Test
public void set_and_get_project() {
AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl();
assertThat(underTest.isShortLivingBranch()).isTrue();
}
+
+ @Test
+ public void getPullRequestBranch_returns_true() {
+ Branch branch = mock(Branch.class);
+ when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+
+ AnalysisMetadataHolderImpl underTest = new AnalysisMetadataHolderImpl();
+ underTest.setBranch(branch);
+
+ assertThat(underTest.isPullRequest()).isTrue();
+ }
}
private final InitializedProperty<Branch> branch = new InitializedProperty<>();
+ private final InitializedProperty<String> pullRequestId = new InitializedProperty<>();
+
private final InitializedProperty<Project> project = new InitializedProperty<>();
private final InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>();
return branch.getProperty();
}
+ @Override
+ public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) {
+ this.pullRequestId.setProperty(pullRequestId);
+ return this;
+ }
+
+ @Override
+ public String getPullRequestId() {
+ checkState(pullRequestId.isInitialized(), "Pull request id has not been set");
+ return pullRequestId.getProperty();
+ }
+
@Override
public AnalysisMetadataHolderRule setProject(Project p) {
this.project.setProperty(p);
Branch property = this.branch.getProperty();
return property != null && property.getType() == BranchType.LONG;
}
+
+ @Override
+ public boolean isPullRequest() {
+ Branch property = this.branch.getProperty();
+ return property != null && property.getType() == BranchType.PULL_REQUEST;
+ }
}
return this;
}
+ @Override
+ public String getPullRequestId() {
+ return delegate.getPullRequestId();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setPullRequestId(String pullRequestId) {
+ delegate.setPullRequestId(pullRequestId);
+ return this;
+ }
+
@Override
public MutableAnalysisMetadataHolderRule setProject(@Nullable Project project) {
delegate.setProject(project);
public boolean isLongLivingBranch() {
return delegate.isLongLivingBranch();
}
+
+ @Override
+ public boolean isPullRequest() {
+ return delegate.isPullRequest();
+ }
}
throw new UnsupportedOperationException();
}
+ @Override
+ public String getPullRequestId() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) {
throw new UnsupportedOperationException();
assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
+ }
+
+ @Test
+ public void persist_pull_request_data() {
+ String pullRequestId = "pr-123";
+ analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, pullRequestId));
+ analysisMetadataHolder.setPullRequestId(pullRequestId);
+ treeRootHolder.setRoot(BRANCH);
+
+ // add main branch in project table and in metadata
+ ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey());
+ analysisMetadataHolder.setProject(Project.copyOf(dto));
+ dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto);
+ // this should add new columns in project and project_branches
+ underTest.persist(dbTester.getSession());
+
+ dbTester.getSession().commit();
+
+ assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
+ assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
+ assertThat(dbTester.countSql("select count(*) from project_branches where pull_request_binary is not null")).isEqualTo(1);
}
private static Branch createBranch(BranchType type, boolean isMain, String name) {
assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1);
}
+ @Test
+ public void should_find_components_with_issues_to_merge_on_derived_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ setRootUuid(project.uuid());
+
+ ComponentDto pullRequest = db.components().insertProjectBranch(project,
+ b -> b.setBranchType(BranchType.PULL_REQUEST),
+ b -> b.setMergeBranchUuid(project.uuid()));
+
+ RuleDefinitionDto rule = db.rules().insert();
+
+ ComponentDto fileWithResolvedIssue = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null));
+ db.issues().insertIssue(IssueTesting.newIssue(rule, pullRequest, fileWithResolvedIssue).setStatus("RESOLVED"));
+
+ underTest = new ShortBranchComponentsWithIssues(treeRootHolder, db.getDbClient());
+
+ assertThat(underTest.getUuids(fileWithResolvedIssue.getKey())).hasSize(1);
+ }
+
@Test
public void should_not_find_components_with_issues_to_merge_on_derived_long() {
ComponentDto project = db.components().insertMainBranch();
verifyZeroInteractions(tracker);
verifyZeroInteractions(mergeBranchTracker);
}
+
+ @Test
+ public void delegate_pull_request_tracker() {
+ Branch branch = mock(Branch.class);
+ when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+ when(analysisMetadataHolder.getBranch()).thenReturn(mock(Branch.class));
+ when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(true);
+
+ underTest.track(component);
+
+ verify(shortBranchTracker).track(component);
+ verifyZeroInteractions(tracker);
+ verifyZeroInteractions(mergeBranchTracker);
+ }
}
assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(qualityGate);
}
+ @Test
+ public void add_hardcoded_QG_on_pull_request() {
+ when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
+ QualityGate qualityGate = mock(QualityGate.class);
+ when(qualityGateService.findById(ShortLivingBranchQualityGate.ID)).thenReturn(Optional.of(qualityGate));
+
+ underTest.execute();
+
+ assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(qualityGate);
+ }
+
@Test
public void execute_sets_default_QualityGate_when_project_has_no_settings() {
when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().asConfig());
verifyZeroInteractions(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService);
}
+
+ @Test
+ public void no_alert_on_pull_request_branches() {
+ Branch shortBranch = mock(Branch.class);
+ when(shortBranch.getType()).thenReturn(BranchType.PULL_REQUEST);
+ analysisMetadataHolder.setBranch(shortBranch);
+ TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
+ MetricRepository metricRepository = mock(MetricRepository.class);
+ MeasureRepository measureRepository = mock(MeasureRepository.class);
+ EventRepository eventRepository = mock(EventRepository.class);
+ NotificationService notificationService = mock(NotificationService.class);
+
+ QualityGateEventsStep underTest = new QualityGateEventsStep(treeRootHolder, metricRepository, measureRepository,
+ eventRepository, notificationService, analysisMetadataHolder);
+
+ underTest.execute();
+
+ verifyZeroInteractions(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService);
+ }
}
return false;
}
+ @Override
+ public String getPullRequestId() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) {
String moduleKey = module.getKey();
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newBranchDto;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
public class SendIssueNotificationsStepTest extends BaseStepTest {
private static final String BRANCH_NAME = "feature";
+ private static final String PULL_REQUEST_ID = "pr-123";
private static final long ANALYSE_DATE = 123L;
private static final int FIVE_MINUTES_IN_MS = 1000 * 60 * 5;
underTest.execute();
verify(notificationService).deliver(newIssuesNotificationMock);
- verify(newIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null);
+ verify(newIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null, null);
verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any());
verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
underTest.execute();
verify(notificationService).deliver(newIssuesNotificationMock);
- verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME);
+ verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME, null);
+ verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
+ verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
+ verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
+ }
+
+ @Test
+ public void send_global_new_issues_notification_on_pull_request() {
+ ComponentDto branch = setUpProjectWithBranch();
+ issueCache.newAppender().append(
+ new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
+ when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), SendIssueNotificationsStep.NOTIF_TYPES)).thenReturn(true);
+ analysisMetadataHolder.setBranch(newPullRequest());
+ analysisMetadataHolder.setPullRequestId(PULL_REQUEST_ID);
+
+ underTest.execute();
+
+ verify(notificationService).deliver(newIssuesNotificationMock);
+ verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), null, PULL_REQUEST_ID);
verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
verify(notificationService).deliver(newIssuesNotificationMock);
verify(notificationService).deliver(myNewIssuesNotificationMock);
verify(myNewIssuesNotificationMock).setAssignee(ISSUE_ASSIGNEE);
- verify(myNewIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null);
+ verify(myNewIssuesNotificationMock).setProject(PROJECT.getPublicKey(), PROJECT.getName(), null, null);
verify(myNewIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any(NewIssuesStatistics.Stats.class));
verify(myNewIssuesNotificationMock).setDebt(ISSUE_DURATION);
private NewIssuesNotification createNewIssuesNotificationMock() {
NewIssuesNotification notification = mock(NewIssuesNotification.class);
- when(notification.setProject(any(), any(), any())).thenReturn(notification);
+ when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
when(notification.setProjectVersion(any())).thenReturn(notification);
when(notification.setAnalysisDate(any())).thenReturn(notification);
when(notification.setStatistics(any(), any())).thenReturn(notification);
private MyNewIssuesNotification createMyNewIssuesNotificationMock() {
MyNewIssuesNotification notification = mock(MyNewIssuesNotification.class);
when(notification.setAssignee(any())).thenReturn(notification);
- when(notification.setProject(any(), any(), any())).thenReturn(notification);
+ when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
when(notification.setProjectVersion(any())).thenReturn(notification);
when(notification.setAnalysisDate(any())).thenReturn(notification);
when(notification.setStatistics(any(), any())).thenReturn(notification);
return branch;
}
+ private static Branch newPullRequest() {
+ Branch branch = mock(Branch.class);
+ when(branch.isMain()).thenReturn(false);
+ when(branch.getType()).thenReturn(PULL_REQUEST);
+ when(branch.getName()).thenReturn(BRANCH_NAME);
+ when(branch.getPullRequestId()).thenReturn(PULL_REQUEST_ID);
+ return branch;
+ }
+
private ComponentDto setUpProjectWithBranch() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto());
ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME));
*/
package org.sonar.server.duplication.ws;
-import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.util.List;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import static java.lang.String.format;
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
- assertThat(parser.parse(db.getSession(), file, null, null)).isEmpty();
+ assertThat(parser.parse(db.getSession(), file, null, null, null)).isEmpty();
}
@Test
public void duplication_on_same_file() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null,
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, null,
format("<duplications>\n" +
" <g>\n" +
" <b s=\"31\" l=\"5\" r=\"%s\"/>\n" +
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file1 = db.components().insertComponent(newFileDto(project));
ComponentDto file2 = db.components().insertComponent(newFileDto(project));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null,
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null,
format("<duplications>\n" +
" <g>\n" +
" <b s=\"20\" l=\"5\" r=\"%s\"/>\n" +
ComponentDto file2 = db.components().insertComponent(newFileDto(project1));
ComponentDto project2 = db.components().insertPrivateProject();
ComponentDto fileOnProject2 = db.components().insertComponent(newFileDto(project2));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null,
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null,
format("<duplications>\n" +
" <g>\n" +
" <b s=\"148\" l=\"24\" r=\"%s\"/>\n" +
ComponentDto file2 = db.components().insertComponent(newFileDto(project2)
.setDbKey("com.sonarsource.orchestrator:sonar-orchestrator:src/main/java/com/sonar/orchestrator/util/CommandExecutor.java")
.setLongName("CommandExecutor"));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null,
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, null,
format("<duplications>\n" +
" <g>\n" +
" <b s=\"94\" l=\"101\" r=\"%s\"/>\n" +
public void duplication_on_not_existing_file() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null,
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file, null, null,
format("<duplications>\n" +
" <g>\n" +
" <b s=\"20\" l=\"5\" r=\"%s\"/>\n" +
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto file1 = db.components().insertComponent(newFileDto(branch));
ComponentDto file2 = db.components().insertComponent(newFileDto(branch));
- List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, branch.getBranch(),
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, branch.getBranch(), null,
+ format("<duplications>\n" +
+ " <g>\n" +
+ " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" +
+ " <b s=\"31\" l=\"5\" r=\"%s\"/>\n" +
+ " </g>\n" +
+ "</duplications>", file2.getDbKey(), file1.getDbKey()));
+ assertThat(blocks).hasSize(1);
+
+ List<DuplicationsParser.Duplication> duplications = blocks.get(0).getDuplications();
+ assertThat(duplications).hasSize(2);
+
+ // Current file comes first
+ DuplicationsParser.Duplication duplication1 = duplications.get(0);
+ assertThat(duplication1.file()).isEqualTo(file1);
+ assertThat(duplication1.file().getKey()).isEqualTo(file1.getKey());
+ assertThat(duplication1.from()).isEqualTo(31);
+ assertThat(duplication1.size()).isEqualTo(5);
+
+ DuplicationsParser.Duplication duplication2 = duplications.get(1);
+ assertThat(duplication2.file()).isEqualTo(file2);
+ assertThat(duplication2.file().getKey()).isEqualTo(file2.getKey());
+ assertThat(duplication2.from()).isEqualTo(20);
+ assertThat(duplication2.size()).isEqualTo(5);
+ }
+
+ @Test
+ public void duplication_on_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
+ ComponentDto file1 = db.components().insertComponent(newFileDto(pullRequest));
+ ComponentDto file2 = db.components().insertComponent(newFileDto(pullRequest));
+ List<DuplicationsParser.Block> blocks = parser.parse(db.getSession(), file1, null, pullRequest.getPullRequest(),
format("<duplications>\n" +
" <g>\n" +
" <b s=\"20\" l=\"5\" r=\"%s\"/>\n" +
}
private static DuplicationsParser.Duplication duplication(List<DuplicationsParser.Duplication> duplications, @Nullable final String componentKey) {
- return Iterables.find(duplications, new Predicate<DuplicationsParser.Duplication>() {
- @Override
- public boolean apply(@Nullable DuplicationsParser.Duplication input) {
- return input != null && (componentKey == null ? input.file() == null : input.file() != null && componentKey.equals(input.file().getDbKey()));
- }
- });
+ return Iterables.find(duplications, input -> input != null && (componentKey == null ? input.file() == null
+ : input.file() != null && componentKey.equals(input.file().getDbKey())));
}
}
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.organization.OrganizationDto;
assertThat(show.since()).isEqualTo("4.4");
assertThat(show.isInternal()).isFalse();
assertThat(show.responseExampleAsString()).isNotEmpty();
- assertThat(show.params()).hasSize(3);
+ assertThat(show.params()).hasSize(4);
}
@Test
file.getKey(), file.longName(), file.uuid(), branch.getKey(), branch.uuid(), project.longName(), file.getBranch()));
}
+ @Test
+ public void duplications_by_file_key_and_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSessionRule.addProjectPermission(UserRole.CODEVIEWER, project);
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
+ ComponentDto file = db.components().insertComponent(newFileDto(pullRequest));
+ db.measures().insertLiveMeasure(file, dataMetric, m -> m.setData(format("<duplications>\n" +
+ " <g>\n" +
+ " <b s=\"31\" l=\"5\" r=\"%s\"/>\n" +
+ " <b s=\"20\" l=\"5\" r=\"%s\"/>\n" +
+ " </g>\n" +
+ "</duplications>\n", file.getDbKey(), file.getDbKey())));
+
+ String result = ws.newRequest()
+ .setParam("key", file.getKey())
+ .setParam("pullRequest", pullRequest.getPullRequest())
+ .execute()
+ .getInput();
+
+ assertJson(result).isSimilarTo(
+ format("{\n" +
+ " \"duplications\": [\n" +
+ " {\n" +
+ " \"blocks\": [\n" +
+ " {\n" +
+ " \"from\": 20,\n" +
+ " \"size\": 5,\n" +
+ " \"_ref\": \"1\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"from\": 31,\n" +
+ " \"size\": 5,\n" +
+ " \"_ref\": \"1\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ],\n" +
+ " \"files\": {\n" +
+ " \"1\": {\n" +
+ " \"key\": \"%s\",\n" +
+ " \"name\": \"%s\",\n" +
+ " \"uuid\": \"%s\",\n" +
+ " \"project\": \"%s\",\n" +
+ " \"projectUuid\": \"%s\",\n" +
+ " \"projectName\": \"%s\"\n" +
+ " \"pullRequest\": \"%s\"\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ file.getKey(), file.longName(), file.uuid(), pullRequest.getKey(), pullRequest.uuid(), project.longName(), file.getPullRequest()));
+ }
+
@Test
public void fail_if_file_does_not_exist() {
expectedException.expect(NotFoundException.class);
import org.sonar.test.JsonAssert;
import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
new DuplicationsParser.Duplication(file1, 57, 12),
new DuplicationsParser.Duplication(file2, 73, 12))));
- test(blocks, null,
+ test(blocks, null, null,
"{\n" +
" \"duplications\": [\n" +
" {\n" +
new DuplicationsParser.Duplication(file1, 57, 12),
new DuplicationsParser.Duplication(file2, 73, 12))));
- test(blocks, null,
+ test(blocks, null, null,
"{\n" +
" \"duplications\": [\n" +
" {\n" +
// Duplication on a removed file
new DuplicationsParser.Duplication(null, 73, 12))));
- test(blocks, null,
+ test(blocks, null, null,
"{\n" +
" \"duplications\": [\n" +
" {\n" +
new DuplicationsParser.Duplication(file1, 57, 12),
new DuplicationsParser.Duplication(file2, 73, 12))));
- test(blocks, branch.getBranch(),
+ test(blocks, branch.getBranch(), null,
"{\n" +
" \"duplications\": [\n" +
" {\n" +
"}");
}
+ @Test
+ public void write_duplications_on_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto file1 = db.components().insertComponent(newFileDto(pullRequest));
+ ComponentDto file2 = db.components().insertComponent(newFileDto(pullRequest));
+ List<DuplicationsParser.Block> blocks = newArrayList();
+ blocks.add(new DuplicationsParser.Block(newArrayList(
+ new DuplicationsParser.Duplication(file1, 57, 12),
+ new DuplicationsParser.Duplication(file2, 73, 12))));
+
+ test(blocks, null, pullRequest.getPullRequest(),
+ "{\n" +
+ " \"duplications\": [\n" +
+ " {\n" +
+ " \"blocks\": [\n" +
+ " {\n" +
+ " \"from\": 57, \"size\": 12, \"_ref\": \"1\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"from\": 73, \"size\": 12, \"_ref\": \"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }," +
+ " ],\n" +
+ " \"files\": {\n" +
+ " \"1\": {\n" +
+ " \"key\": \"" + file1.getKey() + "\",\n" +
+ " \"name\": \"" + file1.longName() + "\",\n" +
+ " \"project\": \"" + pullRequest.getKey() + "\",\n" +
+ " \"projectName\": \"" + pullRequest.longName() + "\",\n" +
+ " \"pullRequest\": \"" + pullRequest.getPullRequest() + "\",\n" +
+ " },\n" +
+ " \"2\": {\n" +
+ " \"key\": \"" + file2.getKey() + "\",\n" +
+ " \"name\": \"" + file2.longName() + "\",\n" +
+ " \"project\": \"" + pullRequest.getKey() + "\",\n" +
+ " \"projectName\": \"" + pullRequest.longName() + "\",\n" +
+ " \"pullRequest\": \"" + pullRequest.getPullRequest() + "\",\n" +
+ " }\n" +
+ " }" +
+ "}");
+ }
+
@Test
public void write_nothing_when_no_data() {
- test(Collections.emptyList(), null, "{\"duplications\": [], \"files\": {}}");
+ test(Collections.emptyList(), null, null,"{\"duplications\": [], \"files\": {}}");
}
- private void test(List<DuplicationsParser.Block> blocks, @Nullable String branch, String expected) {
+ private void test(List<DuplicationsParser.Block> blocks, @Nullable String branch, @Nullable String pullRequest, String expected) {
StringWriter output = new StringWriter();
JsonWriter jsonWriter = JsonWriter.of(output);
- ProtobufJsonFormat.write(underTest.build(db.getSession(), blocks, branch), jsonWriter);
+ ProtobufJsonFormat.write(underTest.build(db.getSession(), blocks, branch, pullRequest), jsonWriter);
JsonAssert.assertJson(output.toString()).isSimilarTo(expected);
}
@Test
public void set_project_without_branch() {
- IssueChangeNotification result = notification.setProject("MyService", "My Service", null);
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", null, null);
assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
assertThat(result.getFieldValue("branch")).isNull();
@Test
public void set_project_with_branch() {
- IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1");
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1", null);
assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
assertThat(result.getFieldValue("branch")).isEqualTo("feature1");
}
+ @Test
+ public void set_project_with_pull_request() {
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", null, "pr-123");
+ assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
+ assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
+ assertThat(result.getFieldValue("pullRequest")).isEqualTo("pr-123");
+ }
+
@Test
public void set_component() {
IssueChangeNotification result = notification.setComponent(new ComponentDto().setDbKey("MyService").setLongName("My Service"));
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts", null);
+ .setProject("Struts", "org.apache:struts", null, null);
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("Simon");
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts", null);
+ .setProject("Struts", "org.apache:struts", null, null);
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("simon");
@Test
public void set_project_without_branch() {
- underTest.setProject("project-key", "project-long-name", null);
+ underTest.setProject("project-key", "project-long-name", null, null);
assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
@Test
public void set_project_with_branch() {
- underTest.setProject("project-key", "project-long-name", "feature");
+ underTest.setProject("project-key", "project-long-name", "feature", null);
assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_BRANCH)).isEqualTo("feature");
}
+ @Test
+ public void set_project_with_pull_request() {
+ underTest.setProject("project-key", "project-long-name", null, "pr-123");
+
+ assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
+ assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
+ assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PULL_REQUEST)).isEqualTo("pr-123");
+ }
+
@Test
public void set_project_version() {
String version = randomAlphanumeric(5);
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.db.component.BranchType.SHORT;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
public class SearchActionComponentsTest {
userSession.addProjectPermission(UserRole.USER, project);
ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
- ComponentDto branch = db.components().insertProjectBranch(project);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT));
ComponentDto branchFile = db.components().insertComponent(newFileDto(branch));
IssueDto branchIssue = db.issues().insertIssue(newIssue(rule, branch, branchFile));
allowAnyoneOnProjects(project);
tuple(branch.getKey(), branch.getBranch()));
}
+ @Test
+ public void search_by_pull_request() {
+ RuleDefinitionDto rule = db.rules().insert();
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
+ IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto pullRequestFile = db.components().insertComponent(newFileDto(pullRequest));
+ IssueDto pullRequestIssue = db.issues().insertIssue(newIssue(rule, pullRequest, pullRequestFile));
+ allowAnyoneOnProjects(project);
+ indexIssuesAndViews();
+
+ SearchWsResponse result = ws.newRequest()
+ .setParam(PARAM_COMPONENT_KEYS, pullRequest.getKey())
+ .setParam(PARAM_PULL_REQUEST, pullRequest.getPullRequest())
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(result.getIssuesList())
+ .extracting(Issue::getKey, Issue::getComponent, Issue::getPullRequest)
+ .containsExactlyInAnyOrder(tuple(pullRequestIssue.getKey(), pullRequestFile.getKey(), pullRequestFile.getPullRequest()));
+ assertThat(result.getComponentsList())
+ .extracting(Issues.Component::getKey, Issues.Component::getPullRequest)
+ .containsExactlyInAnyOrder(
+ tuple(pullRequestFile.getKey(), pullRequestFile.getPullRequest()),
+ tuple(pullRequest.getKey(), pullRequest.getPullRequest()));
+ }
+
@Test
public void search_using_main_branch_name() {
RuleDefinitionDto rule = db.rules().insert();
assertThat(def.params()).extracting("key").containsExactlyInAnyOrder(
"additionalFields", "asc", "assigned", "assignees", "authors", "componentKeys", "componentRootUuids", "componentRoots", "componentUuids", "components", "branch",
- "organization",
+ "pullRequest", "organization",
"createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "languages", "moduleUuids", "onComponentOnly",
"p", "projectUuids", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod",
"statuses", "tags", "types");
markProjectAsAnalyzed(project);
db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(Metric.Level.WARN.name()));
db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
- BranchDto branch = db.getDbClient().branchDao().selectByKey(db.getSession(), project.projectUuid(), "master")
+ BranchDto branch = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), project.projectUuid(), "master")
.orElseThrow(() -> new IllegalStateException("Can't find master branch"));
List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
assertThat(result).isSameAs(ShortLivingBranchQualityGate.GATE);
}
+ @Test
+ public void loadQualityGate_returns_hardcoded_gate_for_pull_requests() {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertPublicProject(organization);
+ BranchDto pullRequest = newBranchDto(project).setBranchType(BranchType.PULL_REQUEST);
+ db.components().insertProjectBranch(project, pullRequest);
+
+ QualityGate result = underTest.loadQualityGate(db.getSession(), organization, project, pullRequest);
+
+ assertThat(result).isSameAs(ShortLivingBranchQualityGate.GATE);
+ }
+
@Test
public void loadQualityGate_on_long_branch_returns_organization_default_gate() {
OrganizationDto organization = db.organizations().insert();
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.INT;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonar.server.component.ws.MeasuresWsParameters.DEPRECATED_PARAM_COMPONENT_ID;
assertThat(def.since()).isEqualTo("5.4");
assertThat(def.params()).extracting(Param::key)
- .containsExactlyInAnyOrder("componentId", "component", "branch", "metricKeys", "additionalFields", "developerId", "developerKey");
+ .containsExactlyInAnyOrder("componentId", "component", "branch", "pullRequest", "metricKeys", "additionalFields", "developerId", "developerKey");
assertThat(def.param("developerId").deprecatedSince()).isEqualTo("6.4");
assertThat(def.param("developerKey").deprecatedSince()).isEqualTo("6.4");
assertThat(def.param("componentId").deprecatedSince()).isEqualTo("6.6");
.containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue()));
}
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
+ SnapshotDto analysis = db.components().insertSnapshot(branch);
+ ComponentDto file = db.components().insertComponent(newFileDto(branch));
+ MetricDto complexity = db.measures().insertMetric(m1 -> m1.setKey("complexity").setValueType(INT.name()));
+ LiveMeasureDto measure = db.measures().insertLiveMeasure(file, complexity, m -> m.setValue(12.0d).setVariation(2.0d));
+
+ ComponentWsResponse response = ws.newRequest()
+ .setParam(PARAM_COMPONENT, file.getKey())
+ .setParam(PARAM_PULL_REQUEST, "pr-123")
+ .setParam(PARAM_METRIC_KEYS, complexity.getKey())
+ .executeProtobuf(ComponentWsResponse.class);
+
+ assertThat(response.getComponent()).extracting(Component::getKey, Component::getPullRequest)
+ .containsExactlyInAnyOrder(file.getKey(), "pr-123");
+ assertThat(response.getComponent().getMeasuresList())
+ .extracting(Measures.Measure::getMetric, m -> parseDouble(m.getValue()))
+ .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue()));
+ }
+
@Test
public void reference_uuid_in_the_response() {
userSession.logIn().setRoot();
db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("'componentId' and 'branch' parameters cannot be used at the same time");
+ expectedException.expectMessage("Parameter 'componentId' cannot be used at the same time as 'branch' or 'pullRequest'");
ws.newRequest()
.setParam(DEPRECATED_PARAM_COMPONENT_ID, file.uuid())
import static org.sonar.api.server.ws.WebService.Param.SORT;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_STRATEGY;
import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY;
.containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue()));
}
+ @Test
+ public void pull_request() {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertPrivateProject(organization);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
+ SnapshotDto analysis = db.components().insertSnapshot(branch);
+ ComponentDto file = db.components().insertComponent(newFileDto(branch));
+ MetricDto complexity = db.measures().insertMetric(m -> m.setValueType(INT.name()));
+ LiveMeasureDto measure = db.measures().insertLiveMeasure(file, complexity, m -> m.setValue(12.0d));
+
+ ComponentTreeWsResponse response = ws.newRequest()
+ .setParam(PARAM_COMPONENT, file.getKey())
+ .setParam(PARAM_PULL_REQUEST, "pr-123")
+ .setParam(PARAM_METRIC_KEYS, complexity.getKey())
+ .executeProtobuf(Measures.ComponentTreeWsResponse.class);
+
+ assertThat(response.getBaseComponent()).extracting(Measures.Component::getKey, Measures.Component::getPullRequest)
+ .containsExactlyInAnyOrder(file.getKey(), "pr-123");
+ assertThat(response.getBaseComponent().getMeasuresList())
+ .extracting(Measures.Measure::getMetric, m -> parseDouble(m.getValue()))
+ .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue()));
+ }
+
@Test
public void return_deprecated_id_in_the_response() {
ComponentDto project = db.components().insertPrivateProject();
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
import static org.sonar.db.component.SnapshotTesting.newAnalysis;
import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
import static org.sonar.db.metric.MetricTesting.newMetricDto;
+import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT;
.containsExactlyInAnyOrder(measure.getValue());
}
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
+ ComponentDto file = db.components().insertComponent(newFileDto(branch));
+ SnapshotDto analysis = db.components().insertSnapshot(branch);
+ MeasureDto measure = db.measures().insertMeasure(file, analysis, nclocMetric, m -> m.setValue(2d));
+
+ SearchHistoryResponse result = ws.newRequest()
+ .setParam(PARAM_COMPONENT, file.getKey())
+ .setParam(PARAM_PULL_REQUEST, "pr-123")
+ .setParam(PARAM_METRICS, "ncloc")
+ .executeProtobuf(SearchHistoryResponse.class);
+
+ assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(1);
+ HistoryMeasure historyMeasure = result.getMeasures(0);
+ assertThat(historyMeasure.getMetric()).isEqualTo(nclocMetric.getKey());
+ assertThat(historyMeasure.getHistoryList())
+ .extracting(m -> parseDouble(m.getValue()))
+ .containsExactlyInAnyOrder(measure.getValue());
+ }
+
@Test
public void fail_when_using_branch_db_key() throws Exception {
OrganizationDto organization = db.organizations().insert();
assertThat(definition.isPost()).isFalse();
assertThat(definition.isInternal()).isFalse();
assertThat(definition.since()).isEqualTo("6.3");
- assertThat(definition.params()).hasSize(7);
+ assertThat(definition.params()).hasSize(8);
Param branch = definition.param("branch");
assertThat(branch.since()).isEqualTo("6.6");
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.SnapshotTesting.newAnalysis;
import static org.sonar.db.event.EventTesting.newEvent;
+import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_PULL_REQUEST;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.WsRequest.Method.POST;
}
+ @Test
+ public void pull_request() {
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
+ SnapshotDto analysis = db.components().insertSnapshot(newAnalysis(branch));
+ EventDto event = db.events().insertEvent(newEvent(analysis).setCategory(EventCategory.QUALITY_GATE.getLabel()));
+
+ List<Analysis> result = call(SearchRequest.builder()
+ .setProject(project.getKey())
+ .setPullRequest("pr-123")
+ .build())
+ .getAnalysesList();
+
+ assertThat(result).extracting(Analysis::getKey).containsExactlyInAnyOrder(analysis.getUuid());
+ assertThat(result.get(0).getEventsList()).extracting(Event::getKey).containsExactlyInAnyOrder(event.getUuid());
+ }
+
@Test
public void empty_response() {
ComponentDto project = db.components().insertPrivateProject();
assertThat(definition.responseExampleAsString()).isNotEmpty();
assertThat(definition.param("project").isRequired()).isTrue();
assertThat(definition.param("category")).isNotNull();
- assertThat(definition.params()).hasSize(7);
+ assertThat(definition.params()).hasSize(8);
Param from = definition.param("from");
assertThat(from.since()).isEqualTo("6.5");
.setMethod(POST.name());
setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
setNullable(wsRequest.getBranch(), branch -> request.setParam(PARAM_BRANCH, branch));
+ setNullable(wsRequest.getPullRequest(), branch -> request.setParam(PARAM_PULL_REQUEST, branch));
setNullable(wsRequest.getCategory(), category -> request.setParam(PARAM_CATEGORY, category.name()));
setNullable(wsRequest.getPage(), page -> request.setParam(Param.PAGE, String.valueOf(page)));
setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(Param.PAGE_SIZE, String.valueOf(pageSize)));
import org.sonar.core.issue.DefaultIssue;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl.ChangedIssueImpl;
+import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doThrow;
verifyNoMoreInteractions(listener1, listener2, listener3);
}
+ @Test
+ public void test_status_mapping() {
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isEqualTo(QGChangeEventListener.Status.OPEN);
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_REOPENED))).isEqualTo(QGChangeEventListener.Status.REOPENED);
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CONFIRMED))).isEqualTo(QGChangeEventListener.Status.CONFIRMED);
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE)))
+ .isEqualTo(QGChangeEventListener.Status.RESOLVED_FP);
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX)))
+ .isEqualTo(QGChangeEventListener.Status.RESOLVED_WF);
+ assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FIXED)))
+ .isEqualTo(QGChangeEventListener.Status.RESOLVED_FIXED);
+ try {
+ ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_CLOSED));
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Unexpected status: CLOSED");
+ }
+ try {
+ ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED));
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).hasMessage("A resolved issue should have a resolution");
+ }
+ try {
+ ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_REMOVED));
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).hasMessage("Unexpected resolution for a resolved issue: REMOVED");
+ }
+ }
+
private void verifyListenerCalled(QGChangeEventListener listener, QGChangeEvent changeEvent, DefaultIssue... issues) {
ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor();
verify(listener).onIssueChanges(same(changeEvent), changedIssuesCaptor.capture());
Set<ChangedIssue> changedIssues = changedIssuesCaptor.getValue();
Tuple[] expected = Arrays.stream(issues)
- .map(issue -> tuple(issue.key(), issue.status(), issue.type()))
+ .map(issue -> tuple(issue.key(), ChangedIssueImpl.statusOf(issue), issue.type()))
.toArray(Tuple[]::new);
assertThat(changedIssues)
.hasSize(issues.length)
- .extracting(ChangedIssue::getKey, t -> t.getStatus().name(), ChangedIssue::getType)
+ .extracting(ChangedIssue::getKey, t -> t.getStatus(), ChangedIssue::getType)
.containsOnly(expected);
}
- private static final String[] STATUSES = Issue.STATUSES.stream().toArray(String[]::new);
+ private static final String[] POSSIBLE_STATUSES = asList(Issue.STATUS_CONFIRMED, Issue.STATUS_REOPENED, Issue.STATUS_RESOLVED).stream().toArray(String[]::new);
private static int issueIdCounter = 0;
private static DefaultIssue newDefaultIssue(String projectUuid) {
defaultIssue.setKey("issue_" + issueIdCounter++);
defaultIssue.setProjectUuid(projectUuid);
defaultIssue.setType(RuleType.values()[new Random().nextInt(RuleType.values().length)]);
- defaultIssue.setStatus(STATUSES[new Random().nextInt(STATUSES.length)]);
+ defaultIssue.setStatus(POSSIBLE_STATUSES[new Random().nextInt(POSSIBLE_STATUSES.length)]);
+ String[] possibleResolutions = possibleResolutions(defaultIssue.getStatus());
+ if (possibleResolutions.length > 0) {
+ defaultIssue.setResolution(possibleResolutions[new Random().nextInt(possibleResolutions.length)]);
+ }
return defaultIssue;
}
+ private static String[] possibleResolutions(String status) {
+ switch (status) {
+ case Issue.STATUS_RESOLVED:
+ return new String[] {Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX};
+ default:
+ return new String[0];
+ }
+ }
+
private static ComponentDto newComponentDto(String uuid) {
ComponentDto componentDto = new ComponentDto();
componentDto.setUuid(uuid);
assertThat(action.isInternal()).isFalse();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
- assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "branch");
+ assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("component", "branch", "pullRequest");
}
@Test
assertThat(action.isInternal()).isFalse();
assertThat(action.isPost()).isTrue();
assertThat(action.responseExampleAsString()).isNull();
- assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("keys", "component", "branch");
+ assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("keys", "component", "branch", "pullRequest");
}
@Test
assertThat(definition.isInternal()).isFalse();
assertThat(definition.since()).isEqualTo("6.1");
assertThat(definition.params()).extracting(Param::key)
- .containsOnly("key", "value", "values", "fieldValues", "component", "branch");
+ .containsOnly("key", "value", "values", "fieldValues", "component", "branch", "pullRequest");
Param branch = definition.param("branch");
assertThat(branch.isInternal()).isTrue();
assertThat(action.isInternal()).isFalse();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
- assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("keys", "component", "branch");
+ assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("keys", "component", "branch", "pullRequest");
}
private ValuesWsResponse executeRequestForComponentProperties(ComponentDto componentDto, String... keys) {
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
public class LinesActionTest {
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
- SourceService sourceService;
- HtmlSourceDecorator htmlSourceDecorator;
- ComponentDao componentDao;
+ private SourceService sourceService;
+ private HtmlSourceDecorator htmlSourceDecorator;
+ private ComponentDao componentDao;
- ComponentDto project;
- ComponentDto file;
+ private ComponentDto project;
+ private ComponentDto file;
- WsTester wsTester;
+ private WsTester wsTester;
@Before
public void setUp() {
request.execute().assertJson(getClass(), "show_source.json");
}
+ @Test
+ public void pull_request() throws Exception {
+ ComponentDto project = db.components().insertMainBranch();
+ userSession.addProjectPermission(UserRole.USER, project);
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto file = db.components().insertComponent(newFileDto(branch));
+ db.getDbClient().fileSourceDao().insert(db.getSession(), new FileSourceDto()
+ .setProjectUuid(branch.uuid())
+ .setFileUuid(file.uuid())
+ .setSourceData(FileSourceTesting.newFakeData(3).build()));
+ db.commit();
+ userSession.logIn("login").addProjectPermission(UserRole.CODEVIEWER, project, file);
+
+ WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines")
+ .setParam("key", file.getKey())
+ .setParam("pullRequest", file.getPullRequest());
+
+ request.execute().assertJson(getClass(), "show_source.json");
+ }
+
@Test
public void fail_when_no_uuid_or_key_param() throws Exception {
expectedException.expect(IllegalArgumentException.class);
db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("'uuid' and 'branch' parameters cannot be used at the same time");
+ expectedException.expectMessage("Parameter 'uuid' cannot be used at the same time as 'branch' or 'pullRequest'");
wsTester.newGetRequest("api/sources", "lines")
.setParam("uuid", file.uuid())
assertThat(raw.since()).isEqualTo("5.0");
assertThat(raw.isInternal()).isFalse();
assertThat(raw.responseExampleAsString()).isNotEmpty();
- assertThat(raw.params()).hasSize(2);
+ assertThat(raw.params()).hasSize(3);
WebService.Action lines = controller.action("lines");
assertThat(lines).isNotNull();
assertThat(lines.since()).isEqualTo("5.0");
assertThat(lines.isInternal()).isTrue();
assertThat(lines.responseExampleAsString()).isNotEmpty();
- assertThat(lines.params()).hasSize(5);
+ assertThat(lines.params()).hasSize(6);
WebService.Action hash = controller.action("hash");
assertThat(hash).isNotNull();
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
import static org.sonar.api.web.UserRole.CODEVIEWER;
import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.protobuf.DbFileSources.Test.TestStatus.OK;
import static org.sonar.server.test.db.TestTesting.newTest;
+import static org.sonar.server.test.ws.ListAction.PARAM_PULL_REQUEST;
import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_ID;
import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_KEY;
import static org.sonar.server.test.ws.ListAction.SOURCE_FILE_LINE_NUMBER;
assertThat(action.isPost()).isFalse();
assertThat(action.handler()).isNotNull();
assertThat(action.responseExampleAsString()).isNotEmpty();
- assertThat(action.params()).hasSize(9);
+ assertThat(action.params()).hasSize(10);
assertThat(action.description()).isEqualTo("Get the list of tests either in a test file or that test a given line of source code.<br /> " +
"Requires 'Browse' permission on the file's project.<br /> " +
"One (and only one) of the following combination of parameters must be provided: " +
tuple(test2.getUuid(), testFile.getKey(), testFile.getBranch()));
}
+ @Test
+ public void list_tests_by_test_file_key_and_pull_request() {
+ ComponentDto project = db.components().insertMainBranch();
+ userSessionRule.addProjectPermission(CODEVIEWER, project);
+ ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
+ ComponentDto mainFile = db.components().insertComponent(newFileDto(pullRequest));
+ ComponentDto testFile = db.components().insertComponent(newFileDto(pullRequest).setQualifier(UNIT_TEST_FILE));
+
+ DbFileSources.Test test1 = newTest(mainFile, 10).build();
+ DbFileSources.Test test2 = newTest(mainFile, 11).build();
+ insertTests(testFile, test1, test2);
+
+ ListResponse request = call(ws.newRequest()
+ .setParam(TEST_FILE_KEY, testFile.getKey())
+ .setParam(PARAM_PULL_REQUEST, testFile.getPullRequest()));
+
+ assertThat(request.getTestsList())
+ .extracting(Tests.Test::getId, Tests.Test::getFileKey, Tests.Test::getFilePullRequest)
+ .containsOnly(
+ tuple(test1.getUuid(), testFile.getKey(), testFile.getPullRequest()),
+ tuple(test2.getUuid(), testFile.getKey(), testFile.getPullRequest()));
+ }
+
@Test
public void list_tests_by_source_file_uuid_and_line_number() {
userSessionRule.addProjectPermission(CODEVIEWER, project);
assertJson(payload.getJson())
.isSimilarTo("{" +
"\"branch\": {" +
- " \"name\": \"feature/foo\"" +
- " \"type\": \"SHORT\"" +
+ " \"name\": \"feature/foo\"," +
+ " \"type\": \"SHORT\"," +
" \"isMain\": false," +
" \"url\": \"http://foo/project/issues?branch=feature%2Ffoo&id=P1&resolved=false\"" +
"}" +
"}");
}
+ @Test
+ public void create_payload_on_pull_request() {
+ CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+ ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "pr/foo", Branch.Type.PULL_REQUEST), 1_500_000_000_000L, emptyMap());
+
+ WebhookPayload payload = underTest.create(analysis);
+ assertJson(payload.getJson())
+ .isSimilarTo("{" +
+ "\"branch\": {" +
+ " \"name\": \"pr/foo\"," +
+ " \"type\": \"PULL_REQUEST\"," +
+ " \"isMain\": false," +
+ " \"url\": \"http://foo/project/issues?pullRequest=pr%2Ffoo&id=P1&resolved=false\"" +
+ "}" +
+ "}");
+ }
+
@Test
public void create_without_ce_task() {
ProjectAnalysis analysis = newAnalysis(null, null, null, null, emptyMap());
public static final String BRANCHES_DOC_LINK = "https://redirect.sonarsource.com/doc/branches.html";
+ public static final String ORGANIZATION = "sonar.organization";
+
public static final String BRANCH_NAME = "sonar.branch.name";
public static final String BRANCH_TARGET = "sonar.branch.target";
- public static final String ORGANIZATION = "sonar.organization";
+
+ public static final String PULL_REQUEST_KEY = "sonar.pullrequest.key";
+ public static final String PULL_REQUEST_BRANCH = "sonar.pullrequest.branch";
+ public static final String PULL_REQUEST_BASE = "sonar.pullrequest.base";
public static final String LINKS_SOURCES_DEV = "sonar.links.scm_dev";
PropertyDefinition.builder(BRANCH_TARGET)
.name("Optional name of target branch to merge into")
.description(
- "Defines what is the target branch of the branch being analyzed. The main branch cannot have a target. "
- + "If no target is defined for other branches, the main branch is used as a target.")
+ "Defines the target branch of the branch being analyzed. The main branch cannot have a target. "
+ + "If no target is defined, the main branch is used as the target.")
+ .hidden()
+ .build(),
+ PropertyDefinition.builder(PULL_REQUEST_BRANCH)
+ .name("Optional name of pull request")
+ .description("Provide a name for the pull request being analyzed. It might match an existing pull request of the project, otherwise a new pull request will be created.")
+ .hidden()
+ .build(),
+ PropertyDefinition.builder(PULL_REQUEST_BASE)
+ .name("Optional name of target branch to merge into")
+ .description(
+ "Defines the target branch of the pull request being analyzed. "
+ + "If no target is defined, the main branch is used as the target.")
.hidden()
.build());
}
@Test
public void all() {
List<PropertyDefinition> defs = CorePropertyDefinitions.all();
- assertThat(defs).hasSize(57);
+ assertThat(defs).hasSize(59);
}
@Test
public interface Branch {
enum Type {
- LONG, SHORT
+ LONG, SHORT, PULL_REQUEST
}
boolean isMain();
}
public void execute() {
- if (branchConfiguration.isShortLivingBranch()) {
- LOG.info("Skipping CPD calculation for short living branch");
+ if (branchConfiguration.isShortOrPullRequest()) {
+ LOG.info("Skipping CPD calculation for short living branch and pull request");
return;
}
execute(TIMEOUT);
}
private boolean shouldSkipComponent(DefaultInputComponent component, Collection<InputComponent> children) {
- if (component instanceof InputModule && children.isEmpty() && branchConfiguration.isShortLivingBranch()) {
+ if (component instanceof InputModule && children.isEmpty()
+ && (branchConfiguration.isShortOrPullRequest())) {
// no children on a module in short branch analysis -> skip it (except root)
return !moduleHierarchy.isRoot((InputModule) component);
} else if (component instanceof InputDir && children.isEmpty()) {
} else if (component instanceof DefaultInputFile) {
// skip files not marked for publishing
DefaultInputFile inputFile = (DefaultInputFile) component;
- return !inputFile.isPublished() || (branchConfiguration.isShortLivingBranch() && inputFile.status() == Status.SAME);
+ return !inputFile.isPublished() || (branchConfiguration.isShortOrPullRequest() && inputFile.status() == Status.SAME);
}
return false;
}
settings.get(ORGANIZATION).ifPresent(builder::setOrganizationKey);
if (branchConfiguration.branchName() != null) {
- builder.setBranchName(branchConfiguration.branchName());
- builder.setBranchType(toProtobufBranchType(branchConfiguration.branchType()));
- String branchTarget = branchConfiguration.branchTarget();
- if (branchTarget != null) {
- builder.setMergeBranchName(branchTarget);
- }
+ addBranchInformation(builder);
}
+
Optional.ofNullable(rootProject.getBranch()).ifPresent(builder::setDeprecatedBranch);
if (scmConfiguration != null) {
- ScmProvider scmProvider = scmConfiguration.provider();
- if (scmProvider != null) {
- Path projectBasedir = moduleHierarchy.root().getBaseDir();
- try {
- builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir)));
- } catch (UnsupportedOperationException e) {
- LOG.debug(e.getMessage());
- }
- try {
- builder.setScmRevisionId(scmProvider.revisionId(projectBasedir));
- } catch (UnsupportedOperationException e) {
- LOG.debug(e.getMessage());
- }
- }
+ addScmInformation(builder);
}
for (QProfile qp : qProfiles.findAll()) {
writer.writeMetadata(builder.build());
}
+ private void addScmInformation(ScannerReport.Metadata.Builder builder) {
+ ScmProvider scmProvider = scmConfiguration.provider();
+ if (scmProvider != null) {
+ Path projectBasedir = moduleHierarchy.root().getBaseDir();
+ try {
+ builder.setRelativePathFromScmRoot(toSonarQubePath(scmProvider.relativePathFromScmRoot(projectBasedir)));
+ } catch (UnsupportedOperationException e) {
+ LOG.debug(e.getMessage());
+ }
+ try {
+ builder.setScmRevisionId(scmProvider.revisionId(projectBasedir));
+ } catch (UnsupportedOperationException e) {
+ LOG.debug(e.getMessage());
+ }
+ }
+ }
+
+ private void addBranchInformation(ScannerReport.Metadata.Builder builder) {
+ builder.setBranchName(branchConfiguration.branchName());
+ BranchType branchType = toProtobufBranchType(branchConfiguration.branchType());
+ builder.setBranchType(branchType);
+ String branchTarget = branchConfiguration.branchTarget();
+ if (branchTarget != null) {
+ builder.setMergeBranchName(branchTarget);
+ }
+ if (branchType == BranchType.PULL_REQUEST) {
+ builder.setPullRequestKey(branchConfiguration.pullRequestKey());
+ }
+ }
+
private static BranchType toProtobufBranchType(org.sonar.scanner.scan.branch.BranchType branchType) {
+ if (branchType == org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST) {
+ return BranchType.PULL_REQUEST;
+ }
if (branchType == org.sonar.scanner.scan.branch.BranchType.LONG) {
return BranchType.LONG;
}
import static org.sonar.core.config.ScannerProperties.BRANCH_NAME;
import static org.sonar.core.config.ScannerProperties.ORGANIZATION;
import static org.sonar.core.util.FileUtils.deleteQuietly;
+import static org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST;
@ScannerSide
public class ReportPublisher implements Startable {
String branchName = branchConfiguration.branchName();
if (branchName != null) {
- post.setParam(CHARACTERISTIC, "branch=" + branchName);
- post.setParam(CHARACTERISTIC, "branchType=" + branchConfiguration.branchType().name());
+ if (branchConfiguration.branchType() != PULL_REQUEST) {
+ post.setParam(CHARACTERISTIC, "branch=" + branchName);
+ post.setParam(CHARACTERISTIC, "branchType=" + branchConfiguration.branchType().name());
+ } else {
+ post.setParam(CHARACTERISTIC, "pullRequest=" + branchConfiguration.pullRequestKey());
+ }
}
WsResponse response;
@Override
public void publish(ScannerReportWriter writer) {
- if (branchConfiguration.isShortLivingBranch()) {
+ if (branchConfiguration.isShortOrPullRequest()) {
return;
}
final ScannerReport.Test.Builder testBuilder = ScannerReport.Test.newBuilder();
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.AnalysisMode;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.api.utils.MessageException;
import org.sonar.core.component.ComponentKeys;
-import org.sonar.core.config.ScannerProperties;
import org.sonar.scanner.bootstrap.GlobalConfiguration;
import org.sonar.scanner.scan.branch.BranchParamsValidator;
+import static java.lang.String.format;
+import static java.util.Objects.nonNull;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.sonar.core.config.ScannerProperties.BRANCHES_DOC_LINK;
+import static org.sonar.core.config.ScannerProperties.BRANCH_NAME;
+import static org.sonar.core.config.ScannerProperties.BRANCH_TARGET;
+import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BASE;
+import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_BRANCH;
+import static org.sonar.core.config.ScannerProperties.PULL_REQUEST_KEY;
+
/**
* This class aims at validating project reactor
* @since 3.6
String deprecatedBranchName = reactor.getRoot().getBranch();
- if (branchParamsValidator != null) {
- // branch plugin is present
+ if (isBranchFeatureAvailable()) {
branchParamsValidator.validate(validationMessages, deprecatedBranchName);
} else {
validateBranchParamsWhenPluginAbsent(validationMessages);
+ validatePullRequestParamsWhenPluginAbsent(validationMessages);
}
validateBranch(validationMessages, deprecatedBranchName);
}
private void validateBranchParamsWhenPluginAbsent(List<String> validationMessages) {
- for (String param : Arrays.asList(ScannerProperties.BRANCH_NAME, ScannerProperties.BRANCH_TARGET)) {
- if (StringUtils.isNotEmpty(settings.get(param).orElse(null))) {
- validationMessages.add(String.format("To use the property \"%s\", the branch plugin is required but not installed. "
- + "See the documentation of branch support: %s.", param, ScannerProperties.BRANCHES_DOC_LINK));
+ for (String param : Arrays.asList(BRANCH_NAME, BRANCH_TARGET)) {
+ if (isNotEmpty(settings.get(param).orElse(null))) {
+ validationMessages.add(format("To use the property \"%s\", the branch plugin is required but not installed. "
+ + "See the documentation of branch support: %s.", param, BRANCHES_DOC_LINK));
}
}
}
+ private void validatePullRequestParamsWhenPluginAbsent(List<String> validationMessages) {
+ Stream.of(PULL_REQUEST_KEY, PULL_REQUEST_BRANCH, PULL_REQUEST_BASE)
+ .filter(param -> nonNull(settings.get(param).orElse(null)))
+ .forEach(param -> validationMessages.add(format("To use the property \"%s\", the branch plugin is required but not installed. "
+ + "See the documentation of branch support: %s.", param, BRANCHES_DOC_LINK)));
+ }
+
private static void validateModuleIssuesMode(ProjectDefinition moduleDef, List<String> validationMessages) {
if (!ComponentKeys.isValidModuleKeyIssuesMode(moduleDef.getKey())) {
- validationMessages.add(String.format("\"%s\" is not a valid project or module key. "
+ validationMessages.add(format("\"%s\" is not a valid project or module key. "
+ "Allowed characters in issues mode are alphanumeric, '-', '_', '.', '/' and ':', with at least one non-digit.", moduleDef.getKey()));
}
}
private static void validateModule(ProjectDefinition moduleDef, List<String> validationMessages) {
if (!ComponentKeys.isValidModuleKey(moduleDef.getKey())) {
- validationMessages.add(String.format("\"%s\" is not a valid project or module key. "
+ validationMessages.add(format("\"%s\" is not a valid project or module key. "
+ "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", moduleDef.getKey()));
}
String originalVersion = moduleDef.getOriginalVersion();
if (originalVersion != null && originalVersion.length() > 100) {
- validationMessages.add(String.format("\"%s\" is not a valid version name for module \"%s\". " +
+ validationMessages.add(format("\"%s\" is not a valid version name for module \"%s\". " +
"The maximum length for version numbers is 100 characters.", originalVersion, moduleDef.getKey()));
}
}
private static void validateBranch(List<String> validationMessages, @Nullable String branch) {
- if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) {
- validationMessages.add(String.format("\"%s\" is not a valid branch name. "
+ if (isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) {
+ validationMessages.add(format("\"%s\" is not a valid branch name. "
+ "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch));
}
}
+ private boolean isBranchFeatureAvailable() {
+ return branchParamsValidator != null;
+ }
+
}
package org.sonar.scanner.scan;
import com.google.common.annotations.VisibleForTesting;
+import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.scanner.scan.branch.BranchConfigurationProvider;
import org.sonar.scanner.scan.branch.BranchType;
import org.sonar.scanner.scan.branch.ProjectBranchesProvider;
+import org.sonar.scanner.scan.branch.ProjectPullRequestsProvider;
import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
import org.sonar.scanner.scan.filesystem.InputComponentStoreProvider;
import org.sonar.scanner.scan.filesystem.StatusDetection;
new RulesProvider(),
new BranchConfigurationProvider(),
new ProjectBranchesProvider(),
+ new ProjectPullRequestsProvider(),
DefaultAnalysisMode.class,
new ProjectRepositoriesProvider(),
String branchName = props.property(ScannerProperties.BRANCH_NAME);
if (branchName != null) {
BranchConfiguration branchConfig = getComponentByType(BranchConfiguration.class);
- LOG.info("Branch name: {}, type: {}", branchName, toDisplayName(branchConfig.branchType()));
+ LOG.info("Branch name: {}, type: {}", branchName, branchTypeToDisplayName(branchConfig.branchType()));
+ }
+
+ String pullRequestBranch = props.property(ScannerProperties.PULL_REQUEST_BRANCH);
+ if (pullRequestBranch != null) {
+ String pullRequestBase = props.property(ScannerProperties.PULL_REQUEST_BASE);
+ LOG.info("Pull request into {}: {}", pullRequestBaseToDisplayName(pullRequestBase), pullRequestBranch);
}
LOG.debug("Start recursive analysis of project modules");
}
}
- private static String toDisplayName(BranchType branchType) {
+ private static String pullRequestBaseToDisplayName(@Nullable String pullRequestBase) {
+ return pullRequestBase != null ? pullRequestBase : "default branch";
+ }
+
+ private static String branchTypeToDisplayName(BranchType branchType) {
switch (branchType) {
case LONG:
return "long living";
*/
BranchType branchType();
- default boolean isShortLivingBranch() {
- return branchType() == BranchType.SHORT;
+ default boolean isShortOrPullRequest() {
+ return branchType() == BranchType.PULL_REQUEST || branchType() == BranchType.SHORT;
}
/**
/**
* The name of the base branch to determine project repository and changed files.
+ *
+ * Note: this is important for the scanner during the analysis of long living branches.
+ * For short living branches, branchBase is always the same as branchTarget.
+ * For long living branches, branchBase is the target in case of first analysis,
+ * otherwise it's the branch itself.
*/
@CheckForNull
String branchBase();
+
+ /**
+ * The key of the pull request.
+ *
+ * @throws IllegalStateException if this branch configuration is not a pull request.
+ */
+ String pullRequestKey();
}
@ScannerSide
@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
public interface BranchConfigurationLoader {
- BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> remoteSettingsSupplier, ProjectBranches branches);
+ BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> remoteSettingsSupplier, ProjectBranches branches, ProjectPullRequests pullRequests);
}
private BranchConfiguration branchConfiguration = null;
public BranchConfiguration provide(@Nullable BranchConfigurationLoader loader, GlobalConfiguration globalConfiguration, ProjectKey projectKey,
- SettingsLoader settingsLoader, ProjectBranches branches) {
+ SettingsLoader settingsLoader, ProjectBranches branches, ProjectPullRequests pullRequests) {
if (branchConfiguration == null) {
if (loader == null) {
branchConfiguration = new DefaultBranchConfiguration();
} else {
Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
Supplier<Map<String, String>> settingsSupplier = createSettingsSupplier(globalConfiguration, projectKey, settingsLoader);
- branchConfiguration = loader.load(globalConfiguration.getProperties(), settingsSupplier, branches);
+ branchConfiguration = loader.load(globalConfiguration.getProperties(), settingsSupplier, branches, pullRequests);
profiler.stopInfo();
}
}
package org.sonar.scanner.scan.branch;
public enum BranchType {
- SHORT, LONG
+ SHORT, LONG, PULL_REQUEST
}
public String branchBase() {
return null;
}
+
+ @Override
+ public String pullRequestKey() {
+ throw new IllegalStateException("Only a branch of type PULL_REQUEST can have a pull request id.");
+ }
}
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
-import org.sonar.core.config.ScannerProperties;
-import org.sonar.scanner.bootstrap.GlobalConfiguration;
public class ProjectBranchesProvider extends ProviderAdapter {
private ProjectBranches branches = null;
- public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProjectKey projectKey, GlobalConfiguration settings) {
- if (branches == null) {
- if (loader == null || !settings.get(ScannerProperties.BRANCH_NAME).isPresent()) {
- branches = new ProjectBranches(Collections.emptyList());
- } else {
- Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
- branches = loader.load(projectKey.get());
- profiler.stopInfo();
- }
+ public ProjectBranches provide(@Nullable ProjectBranchesLoader loader, ProjectKey projectKey) {
+ if (branches != null) {
+ return branches;
}
+
+ if (loader == null) {
+ branches = new ProjectBranches(Collections.emptyList());
+ return branches;
+ }
+
+ Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
+ branches = loader.load(projectKey.get());
+ profiler.stopInfo();
return branches;
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.scan.branch;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Container class for information about the pull requests of a project.
+ */
+@Immutable
+public class ProjectPullRequests {
+
+ private final Map<String, PullRequestInfo> pullRequestsById;
+
+ public ProjectPullRequests(List<PullRequestInfo> pullRequestsById) {
+ this.pullRequestsById = pullRequestsById.stream().collect(Collectors.toMap(PullRequestInfo::getBranch, Function.identity()));
+ }
+
+ @CheckForNull
+ public PullRequestInfo get(String branch) {
+ return pullRequestsById.get(branch);
+ }
+
+ public boolean isEmpty() {
+ return pullRequestsById.isEmpty();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.scan.branch;
+
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.ScannerSide;
+
+@ScannerSide
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public interface ProjectPullRequestsLoader {
+
+ /**
+ * Load the pull requests of a project.
+ */
+ ProjectPullRequests load(String projectKey);
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.scan.branch;
+
+import java.util.Collections;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.batch.bootstrap.ProjectKey;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+
+public class ProjectPullRequestsProvider extends ProviderAdapter {
+
+ private static final Logger LOG = Loggers.get(ProjectPullRequestsProvider.class);
+ private static final String LOG_MSG = "Load project pull requests";
+
+ private ProjectPullRequests pullRequests = null;
+
+ public ProjectPullRequests provide(@org.picocontainer.annotations.Nullable ProjectPullRequestsLoader loader, ProjectKey projectKey) {
+ if (pullRequests != null) {
+ return pullRequests;
+ }
+
+ if (loader == null) {
+ pullRequests = new ProjectPullRequests(Collections.emptyList());
+ return pullRequests;
+ }
+
+ Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
+ pullRequests = loader.load(projectKey.get());
+ profiler.stopInfo();
+ return pullRequests;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.scan.branch;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Container class for information about a pull request.
+ */
+@Immutable
+public class PullRequestInfo {
+ private final String id;
+ private final String branch;
+ private final String base;
+
+ public PullRequestInfo(String id, String branch, @Nullable String base) {
+ this.id = id;
+ this.branch = branch;
+ this.base = base;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getBranch() {
+ return branch;
+ }
+
+ @CheckForNull
+ public String getBase() {
+ return base;
+ }
+}
return inputFileCache.values().stream()
.map(f -> (DefaultInputFile) f)
.filter(DefaultInputFile::isPublished)
- .filter(f -> (!branchConfiguration.isShortLivingBranch()) || f.status() != Status.SAME)::iterator;
+ .filter(f -> !branchConfiguration.isShortOrPullRequest() || f.status() != Status.SAME)
+ ::iterator;
}
public Iterable<InputFile> allFiles() {
@CheckForNull
private static Collection<Path> loadChangedFilesIfNeeded(ScmConfiguration scmConfiguration, BranchConfiguration branchConfiguration, Path rootBaseDir) {
- if (branchConfiguration.isShortLivingBranch() && branchConfiguration.branchTarget() != null) {
+ if (branchConfiguration.isShortOrPullRequest() && branchConfiguration.branchTarget() != null) {
ScmProvider scmProvider = scmConfiguration.provider();
if (scmProvider != null) {
Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
}
if (configuration.forceReloadAll() || f.status() != Status.SAME) {
addIfNotEmpty(filesToBlame, f);
- } else if (!branchConfiguration.isShortLivingBranch()) {
+ } else if (!branchConfiguration.isShortOrPullRequest()) {
// File status is SAME so that mean fileData exists
FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile.getModuleRelativePath());
if (StringUtils.isEmpty(fileData.revision())) {
@Override
public NewCoverage newCoverage() {
- if (branchConfiguration.isShortLivingBranch()) {
+ if (branchConfiguration.isShortOrPullRequest()) {
return NO_OP_NEW_COVERAGE;
}
return new DefaultCoverage(sensorStorage);
@Override
public NewCpdTokens newCpdTokens() {
- if (analysisMode.isIssues() || branchConfiguration.isShortLivingBranch()) {
+ if (analysisMode.isIssues() || branchConfiguration.isShortOrPullRequest()) {
return NO_OP_NEW_CPD_TOKENS;
}
return new DefaultCpdTokens(config, sensorStorage);
}
private boolean shouldSkipStorage(DefaultInputFile defaultInputFile) {
- return branchConfiguration.isShortLivingBranch() && defaultInputFile.status() == InputFile.Status.SAME;
+ return branchConfiguration.isShortOrPullRequest() && defaultInputFile.status() == InputFile.Status.SAME;
}
/**
@Test
public void skipIfShortBranch() {
- when(branchConfig.isShortLivingBranch()).thenReturn(true);
+ when(branchConfig.isShortOrPullRequest()).thenReturn(true);
+ index = mock(SonarCpdBlockIndex.class);
+ executor = new CpdExecutor(settings, index, publisher, componentStore, branchConfig);
+
+ executor.execute();
+
+ verifyZeroInteractions(index);
+ }
+
+ @Test
+ public void skip_if_pull_request() {
+ when(branchConfig.isShortOrPullRequest()).thenReturn(true);
index = mock(SonarCpdBlockIndex.class);
executor = new CpdExecutor(settings, index, publisher, componentStore, branchConfig);
import org.sonar.scanner.scan.branch.BranchConfigurationLoader;
import org.sonar.scanner.scan.branch.BranchType;
import org.sonar.scanner.scan.branch.ProjectBranches;
+import org.sonar.scanner.scan.branch.ProjectPullRequests;
import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
import org.sonarqube.ws.Rules.ListResponse.Rule;
public String branchBase() {
return branchBase;
}
+
+ @Override
+ public String pullRequestKey() {
+ throw new UnsupportedOperationException();
+ }
}
public ScannerMediumTester setBranchType(BranchType branchType) {
private class FakeBranchConfigurationLoader implements BranchConfigurationLoader {
@Override
- public BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> settingsSupplier, ProjectBranches branches) {
+ public BranchConfiguration load(Map<String, String> localSettings, Supplier<Map<String, String>> settingsSupplier, ProjectBranches branches, ProjectPullRequests pullRequests) {
return branchConfiguration;
}
}
@Test
public void skip_unchanged_components_in_short_branches() throws IOException {
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
+ ProjectAnalysisInfo projectAnalysisInfo = mock(ProjectAnalysisInfo.class);
+ when(projectAnalysisInfo.analysisDate()).thenReturn(DateUtils.parseDate("2012-12-12"));
+
+ Path moduleBaseDir = temp.newFolder().toPath();
+ ProjectDefinition rootDef = ProjectDefinition.create()
+ .setKey("foo")
+ .setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0")
+ .setName("Root project")
+ .setDescription("Root description")
+ .setBaseDir(moduleBaseDir.toFile())
+ .setWorkDir(temp.newFolder());
+ DefaultInputModule root = new DefaultInputModule(rootDef, 1);
+
+ moduleHierarchy = mock(InputModuleHierarchy.class);
+ when(moduleHierarchy.root()).thenReturn(root);
+ when(moduleHierarchy.children(root)).thenReturn(Collections.emptyList());
+
+ // dir with changed files
+ DefaultInputDir dir = new DefaultInputDir("module1", "src", 2)
+ .setModuleBaseDir(moduleBaseDir);
+ tree.index(dir, root);
+
+ // dir without changed files or issues
+ DefaultInputDir dir2 = new DefaultInputDir("module1", "src2", 3)
+ .setModuleBaseDir(moduleBaseDir);
+ tree.index(dir2, root);
+
+ // dir without changed files but has issues
+ DefaultInputDir dir3 = new DefaultInputDir("module1", "src3", 4)
+ .setModuleBaseDir(moduleBaseDir);
+ tree.index(dir3, root);
+ writeIssue(4);
+
+ DefaultInputFile file = new TestInputFileBuilder("module1", "src/Foo.java", 5)
+ .setLines(2)
+ .setPublish(true)
+ .setStatus(InputFile.Status.ADDED)
+ .build();
+ tree.index(file, dir);
+
+ DefaultInputFile file2 = new TestInputFileBuilder("module1", "src2/Foo2.java", 6)
+ .setPublish(true)
+ .setStatus(InputFile.Status.SAME)
+ .setLines(2)
+ .build();
+ tree.index(file2, dir2);
+
+ DefaultInputFile file3 = new TestInputFileBuilder("module1", "src3/Foo3.java", 7)
+ .setPublish(true)
+ .setStatus(InputFile.Status.SAME)
+ .setLines(2)
+ .build();
+ tree.index(file3, dir3);
+
+ ComponentsPublisher publisher = new ComponentsPublisher(moduleHierarchy, tree, branchConfiguration);
+ publisher.publish(writer);
+
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isTrue();
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 2)).isTrue();
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 4)).isTrue();
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 5)).isTrue();
+
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 3)).isFalse();
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 6)).isFalse();
+ assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 7)).isFalse();
+ }
+
+ @Test
+ public void skip_unchanged_components_in_pull_requests() throws IOException {
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
ProjectAnalysisInfo projectAnalysisInfo = mock(ProjectAnalysisInfo.class);
when(projectAnalysisInfo.analysisDate()).thenReturn(DateUtils.parseDate("2012-12-12"));
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.sonar.scanner.scan.branch.BranchType.PULL_REQUEST;
import static org.sonar.scanner.scan.branch.BranchType.SHORT;
public class ReportPublisherTest {
.containsExactlyInAnyOrder("branch=" + branchName, "branchType=" + SHORT.name());
}
+ @Test
+ public void send_pull_request_characteristic() throws Exception {
+ ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+ new ReportPublisherStep[0], branchConfiguration);
+
+ String orgName = "MyOrg";
+ settings.setProperty(ScannerProperties.ORGANIZATION, orgName);
+
+ String branchName = "feature";
+ String pullRequestId = "pr-123";
+ when(branchConfiguration.branchName()).thenReturn(branchName);
+ when(branchConfiguration.branchType()).thenReturn(PULL_REQUEST);
+ when(branchConfiguration.pullRequestKey()).thenReturn(pullRequestId);
+
+ WsResponse response = mock(WsResponse.class);
+
+ PipedOutputStream out = new PipedOutputStream();
+ PipedInputStream in = new PipedInputStream(out);
+ Ce.SubmitResponse.newBuilder().build().writeTo(out);
+ out.close();
+
+ when(response.failIfNotSuccessful()).thenReturn(response);
+ when(response.contentStream()).thenReturn(in);
+
+ when(wsClient.call(any(WsRequest.class))).thenReturn(response);
+ underTest.upload(temp.newFile());
+
+ ArgumentCaptor<WsRequest> capture = ArgumentCaptor.forClass(WsRequest.class);
+ verify(wsClient).call(capture.capture());
+
+ WsRequest wsRequest = capture.getValue();
+ assertThat(wsRequest.getParameters().getKeys()).hasSize(3);
+ assertThat(wsRequest.getParameters().getValues("organization")).containsExactly(orgName);
+ assertThat(wsRequest.getParameters().getValues("projectKey")).containsExactly("struts");
+ assertThat(wsRequest.getParameters().getValues("characteristic"))
+ .containsExactlyInAnyOrder("pullRequest=" + pullRequestId);
+ }
+
}
@Test
public void do_nothing_for_short_living_branches() throws IOException {
BranchConfiguration branchConfiguration = mock(BranchConfiguration.class);
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
+ InputComponentStore componentStore = mock(InputComponentStore.class);
+ TestExecutionAndCoveragePublisher publisher = new TestExecutionAndCoveragePublisher(componentStore, null, branchConfiguration);
+ File outputDir = temp.newFolder();
+ ScannerReportWriter writer = new ScannerReportWriter(outputDir);
+
+ publisher.publish(writer);
+
+ verifyZeroInteractions(componentStore);
+ }
+
+ @Test
+ public void do_nothing_for_pull_requests() throws IOException {
+ BranchConfiguration branchConfiguration = mock(BranchConfiguration.class);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
InputComponentStore componentStore = mock(InputComponentStore.class);
TestExecutionAndCoveragePublisher publisher = new TestExecutionAndCoveragePublisher(componentStore, null, branchConfiguration);
File outputDir = temp.newFolder();
validator.validate(reactor);
}
+ @Test
+ public void fail_when_pull_request_id_specified_but_branch_plugin_not_present() {
+ ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo");
+ ProjectReactor reactor = new ProjectReactor(def);
+
+ when(settings.get(eq(ScannerProperties.PULL_REQUEST_KEY))).thenReturn(Optional.of("#1984"));
+
+ thrown.expect(MessageException.class);
+ thrown.expectMessage("the branch plugin is required but not installed");
+
+ validator.validate(reactor);
+ }
+
+ @Test
+ public void fail_when_pull_request_branch_is_specified_but_branch_plugin_not_present() {
+ ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo");
+ ProjectReactor reactor = new ProjectReactor(def);
+
+ when(settings.get(eq(ScannerProperties.PULL_REQUEST_BRANCH))).thenReturn(Optional.of("feature1"));
+
+ thrown.expect(MessageException.class);
+ thrown.expectMessage("the branch plugin is required but not installed");
+
+ validator.validate(reactor);
+ }
+
+ @Test
+ public void fail_when_pull_request_base_specified_but_branch_plugin_not_present() {
+ ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, "foo");
+ ProjectReactor reactor = new ProjectReactor(def);
+
+ when(settings.get(eq(ScannerProperties.PULL_REQUEST_BASE))).thenReturn(Optional.of("feature1"));
+
+ thrown.expect(MessageException.class);
+ thrown.expectMessage("the branch plugin is required but not installed");
+
+ validator.validate(reactor);
+ }
+
@Test
public void not_fail_with_valid_version() {
validator.validate(createProjectReactor("foo", def -> def.setVersion("1.0")));
private BranchConfigurationLoader loader;
private BranchConfiguration config;
private ProjectBranches branches;
+ private ProjectPullRequests pullRequests;
private ProjectKey projectKey;
private Map<String, String> globalPropertiesMap;
private Map<String, String> remoteProjectSettings;
loader = mock(BranchConfigurationLoader.class);
config = mock(BranchConfiguration.class);
branches = mock(ProjectBranches.class);
+ pullRequests = mock(ProjectPullRequests.class);
settingsLoader = mock(SettingsLoader.class);
projectKey = mock(ProjectKey.class);
globalPropertiesMap = new HashMap<>();
@Test
public void should_cache_config() {
- BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches);
- assertThat(provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches)).isSameAs(configuration);
+ BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests);
+ assertThat(provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests)).isSameAs(configuration);
}
@Test
public void should_use_loader() {
- when(loader.load(eq(globalPropertiesMap), any(Supplier.class), eq(branches))).thenReturn(config);
- BranchConfiguration branchConfig = provider.provide(loader, globalConfiguration, projectKey, settingsLoader, branches);
+ when(loader.load(eq(globalPropertiesMap), any(Supplier.class), eq(branches), eq(pullRequests))).thenReturn(config);
- assertThat(branchConfig).isSameAs(config);
+ BranchConfiguration result = provider.provide(loader, globalConfiguration, projectKey, settingsLoader, branches, pullRequests);
+
+ assertThat(result).isSameAs(config);
}
@Test
public void should_return_default_if_no_loader() {
- BranchConfiguration configuration = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches);
- assertThat(configuration.branchTarget()).isNull();
- assertThat(configuration.branchType()).isEqualTo(BranchType.LONG);
+ BranchConfiguration result = provider.provide(null, globalConfiguration, projectKey, settingsLoader, branches, pullRequests);
+
+ assertThat(result.branchTarget()).isNull();
+ assertThat(result.branchType()).isEqualTo(BranchType.LONG);
}
}
*/
package org.sonar.scanner.scan.branch;
-import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
-import org.sonar.api.config.Configuration;
-import org.sonar.scanner.bootstrap.GlobalConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
private ProjectBranchesProvider provider = new ProjectBranchesProvider();
private ProjectBranchesLoader mockLoader;
private ProjectBranches mockBranches;
- private GlobalConfiguration mockSettings;
@Before
public void setUp() {
mockLoader = mock(ProjectBranchesLoader.class);
mockBranches = mock(ProjectBranches.class);
- mockSettings = mock(GlobalConfiguration.class);
}
@Test
public void should_cache_branches() {
- ProjectBranches branches = provider.provide(null, () -> "project", mockSettings);
- assertThat(provider.provide(null, () -> "project", mockSettings)).isSameAs(branches);
+ ProjectBranches branches = provider.provide(null, () -> "project");
+ assertThat(provider.provide(null, () -> "project")).isSameAs(branches);
}
@Test
public void should_use_loader() {
when(mockLoader.load("key")).thenReturn(mockBranches);
- when(mockSettings.get(anyString())).thenReturn(Optional.of("somebranch"));
- ProjectBranches branches = provider.provide(mockLoader, () -> "key", mockSettings);
+ ProjectBranches branches = provider.provide(mockLoader, () -> "key");
assertThat(branches).isSameAs(mockBranches);
}
@Test
public void should_return_default_if_no_loader() {
- ProjectBranches branches = provider.provide(null, () -> "project", mockSettings);
+ ProjectBranches branches = provider.provide(null, () -> "project");
assertThat(branches.isEmpty()).isTrue();
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.scan.branch;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ProjectPullRequestsProviderTest {
+ private ProjectPullRequestsProvider provider = new ProjectPullRequestsProvider();
+ private ProjectPullRequestsLoader mockLoader;
+ private ProjectPullRequests pullRequests;
+
+ @Before
+ public void setUp() {
+ mockLoader = mock(ProjectPullRequestsLoader.class);
+ pullRequests = new ProjectPullRequests(emptyList());
+ }
+
+ @Test
+ public void cache_pull_requests() {
+ ProjectPullRequests pullRequests = provider.provide(null, () -> "project");
+
+ assertThat(provider.provide(null, () -> "project")).isSameAs(pullRequests);
+ }
+
+ @Test
+ public void should_use_loader() {
+ when(mockLoader.load("key")).thenReturn(pullRequests);
+
+ ProjectPullRequests result = provider.provide(mockLoader, () -> "key");
+
+ assertThat(result).isSameAs(pullRequests);
+ }
+
+ @Test
+ public void should_return_default_if_no_loader() {
+ ProjectPullRequests result = provider.provide(null, () -> "project");
+
+ assertThat(result.isEmpty()).isTrue();
+ }
+}
@Test
public void testNoScmProvider() {
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
when(branchConfiguration.branchTarget()).thenReturn("target");
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
@Test
public void testFailIfRelativePath() {
when(branchConfiguration.branchTarget()).thenReturn("target");
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
when(scmConfiguration.provider()).thenReturn(scmProvider);
when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singleton(Paths.get("changedFile")));
@Test
public void testProviderDoesntSupport() {
when(branchConfiguration.branchTarget()).thenReturn("target");
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
when(scmConfiguration.provider()).thenReturn(scmProvider);
when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(null);
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
@Test
public void testNoOpInNonShortLivedBranch() {
- when(branchConfiguration.isShortLivingBranch()).thenReturn(false);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(false);
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
assertThat(scmChangedFiles.get()).isNull();
};
when(scmConfiguration.provider()).thenReturn(legacy);
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
when(branchConfiguration.branchTarget()).thenReturn("target");
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
@Test
public void testReturnChangedFiles() {
when(branchConfiguration.branchTarget()).thenReturn("target");
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
when(scmConfiguration.provider()).thenReturn(scmProvider);
when(scmProvider.branchChangedFiles("target", rootBaseDir)).thenReturn(Collections.singleton(Paths.get("changedFile").toAbsolutePath()));
ScmChangedFiles scmChangedFiles = provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
public void testCacheObject() {
provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
provider.provide(scmConfiguration, branchConfiguration, inputModuleHierarchy);
- verify(branchConfiguration).isShortLivingBranch();
+ verify(branchConfiguration).isShortOrPullRequest();
}
}
@Test
public void shouldSkipDupsAndCoverageOnShortBranches() {
- when(branchConfig.isShortLivingBranch()).thenReturn(true);
+ when(branchConfig.isShortOrPullRequest()).thenReturn(true);
+ assertThat(adaptor.newCpdTokens()).isEqualTo(DefaultSensorContext.NO_OP_NEW_CPD_TOKENS);
+ assertThat(adaptor.newCoverage()).isEqualTo(DefaultSensorContext.NO_OP_NEW_COVERAGE);
+ }
+
+ @Test
+ public void shouldSkipDupsAndCoverageOnPullRequests() {
+ when(branchConfig.isShortOrPullRequest()).thenReturn(true);
assertThat(adaptor.newCpdTokens()).isEqualTo(DefaultSensorContext.NO_OP_NEW_CPD_TOKENS);
assertThat(adaptor.newCoverage()).isEqualTo(DefaultSensorContext.NO_OP_NEW_COVERAGE);
}
@Test
public void should_skip_issue_on_short_branch_when_file_status_is_SAME() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
DefaultIssue issue = new DefaultIssue().at(new DefaultIssueLocation().on(file));
underTest.store(issue);
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("// comment")
.setStatus(InputFile.Status.SAME).build();
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
DefaultHighlighting highlighting = new DefaultHighlighting(underTest).onFile(file).highlight(0, 1, TypeOfText.KEYWORD);
underTest.store(highlighting);
@Test
public void should_skip_file_measure_on_short_branch_when_file_status_is_SAME() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
- when(branchConfiguration.isShortLivingBranch()).thenReturn(true);
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
+
+ underTest.store(new DefaultMeasure()
+ .on(file)
+ .forMetric(CoreMetrics.NCLOC)
+ .withValue(10));
+
+ verifyZeroInteractions(measureCache);
+ }
+
+ @Test
+ public void should_skip_file_measure_on_pull_request_when_file_status_is_SAME() {
+ InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
+ when(branchConfiguration.isShortOrPullRequest()).thenReturn(true);
underTest.store(new DefaultMeasure()
.on(file)
string relative_path_from_scm_root = 12;
string scm_revision_id = 13;
+ string pull_request_key = 14;
+
message QProfile {
string key = 1;
string name = 2;
UNSET = 0;
LONG = 1;
SHORT = 2;
+ PULL_REQUEST = 3;
}
}
import org.sonarqube.ws.client.projectanalyses.ProjectAnalysesService;
import org.sonarqube.ws.client.projectbranches.ProjectBranchesService;
import org.sonarqube.ws.client.projectlinks.ProjectLinksService;
-import org.sonarqube.ws.client.projecttags.ProjectTagsService;
+import org.sonarqube.ws.client.projectpullrequests.ProjectPullRequestsService;
import org.sonarqube.ws.client.projects.ProjectsService;
+import org.sonarqube.ws.client.projecttags.ProjectTagsService;
import org.sonarqube.ws.client.properties.PropertiesService;
import org.sonarqube.ws.client.qualitygates.QualitygatesService;
import org.sonarqube.ws.client.qualityprofiles.QualityprofilesService;
private final ProjectAnalysesService projectAnalysesService;
private final ProjectBranchesService projectBranchesService;
private final ProjectLinksService projectLinksService;
+ private final ProjectPullRequestsService projectPullRequestsService;
private final ProjectTagsService projectTagsService;
private final ProjectsService projectsService;
private final PropertiesService propertiesService;
this.projectAnalysesService = new ProjectAnalysesService(wsConnector);
this.projectBranchesService = new ProjectBranchesService(wsConnector);
this.projectLinksService = new ProjectLinksService(wsConnector);
+ this.projectPullRequestsService = new ProjectPullRequestsService(wsConnector);
this.projectTagsService = new ProjectTagsService(wsConnector);
this.projectsService = new ProjectsService(wsConnector);
this.propertiesService = new PropertiesService(wsConnector);
return projectLinksService;
}
+ @Override
+ public ProjectPullRequestsService projectPullRequests() {
+ return projectPullRequestsService;
+ }
+
@Override
public ProjectTagsService projectTags() {
return projectTagsService;
import org.sonarqube.ws.client.projectanalyses.ProjectAnalysesService;
import org.sonarqube.ws.client.projectbranches.ProjectBranchesService;
import org.sonarqube.ws.client.projectlinks.ProjectLinksService;
+import org.sonarqube.ws.client.projectpullrequests.ProjectPullRequestsService;
import org.sonarqube.ws.client.projecttags.ProjectTagsService;
import org.sonarqube.ws.client.projects.ProjectsService;
import org.sonarqube.ws.client.properties.PropertiesService;
ProjectLinksService projectLinks();
+ ProjectPullRequestsService projectPullRequests();
+
ProjectTagsService projectTags();
ProjectsService projects();
public static final String PARAM_COMPONENT_ID = "componentId";
public static final String PARAM_COMPONENT = "component";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
private ComponentsWsParameters() {
// static utility class
private String branch;
private String component;
private String componentId;
+ private String pullRequest;
/**
* This is part of the internal API.
public String getComponentId() {
return componentId;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public AppRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
.setParam("componentId", request.getComponentId())
+ .setParam("pullRequest", request.getPullRequest())
.setMediaType(MediaTypes.JSON)
).content();
}
new GetRequest(path("show"))
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
- .setParam("componentId", request.getComponentId()),
+ .setParam("componentId", request.getComponentId())
+ .setParam("pullRequest", request.getPullRequest()),
ShowWsResponse.parser());
}
.setParam("componentId", request.getComponentId())
.setParam("p", request.getP())
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("q", request.getQ())
.setParam("qualifiers", request.getQualifiers() == null ? null : request.getQualifiers().stream().collect(Collectors.joining(",")))
.setParam("s", request.getS() == null ? null : request.getS().stream().collect(Collectors.joining(",")))
private String branch;
private String component;
private String componentId;
+ private String pullRequest;
/**
* This is part of the internal API.
public String getComponentId() {
return componentId;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ShowRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
private String componentId;
private String p;
private String ps;
+ private String pullRequest;
private String q;
private List<String> qualifiers;
private List<String> s;
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public TreeRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "FILE_NAM"
*/
new GetRequest(path("show"))
.setParam("branch", request.getBranch())
.setParam("key", request.getKey())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("uuid", request.getUuid()),
ShowResponse.parser());
}
private String branch;
private String key;
+ private String pullRequest;
private String uuid;
/**
return key;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ShowRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "584a89f2-8037-4f7b-b82c-8b45d2d63fb2"
* @deprecated since 6.5
public static final String PARAM_FILE_UUIDS = "fileUuids";
public static final String PARAM_ON_COMPONENT_ONLY = "onComponentOnly";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_PULL_REQUEST = "pullRequest";
public static final String PARAM_ORGANIZATION = "organization";
public static final String PARAM_RULES = "rules";
public static final String PARAM_ACTIONS = "actions";
.setParam("projectUuids", request.getProjectUuids() == null ? null : request.getProjectUuids().stream().collect(Collectors.joining(",")))
.setParam("projects", request.getProjects() == null ? null : request.getProjects().stream().collect(Collectors.joining(",")))
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("resolutions", request.getResolutions() == null ? null : request.getResolutions().stream().collect(Collectors.joining(",")))
.setParam("resolved", request.getResolved())
.setParam("rules", request.getRules() == null ? null : request.getRules().stream().collect(Collectors.joining(",")))
private List<String> projectUuids;
private List<String> projects;
private String ps;
+ private String pullRequest;
private List<String> resolutions;
private String resolved;
private List<String> rules;
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public SearchRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "FIXED,REMOVED"
* Possible values:
private String developerId;
private String developerKey;
private List<String> metricKeys;
+ private String pullRequest;
/**
* Example value: "periods,metrics"
public List<String> getMetricKeys() {
return metricKeys;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ComponentRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
private String metricSortFilter;
private String p;
private String ps;
+ private String pullRequest;
private String q;
private List<String> qualifiers;
private List<String> s;
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ComponentTreeRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "FILE_NAM"
*/
.setParam("componentId", request.getComponentId())
.setParam("developerId", request.getDeveloperId())
.setParam("developerKey", request.getDeveloperKey())
- .setParam("metricKeys", request.getMetricKeys() == null ? null : request.getMetricKeys().stream().collect(Collectors.joining(","))),
+ .setParam("metricKeys", request.getMetricKeys() == null ? null : request.getMetricKeys().stream().collect(Collectors.joining(",")))
+ .setParam("pullRequest", request.getPullRequest()),
ComponentWsResponse.parser());
}
.setParam("metricSortFilter", request.getMetricSortFilter())
.setParam("p", request.getP())
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("q", request.getQ())
.setParam("qualifiers", request.getQualifiers() == null ? null : request.getQualifiers().stream().collect(Collectors.joining(",")))
.setParam("s", request.getS() == null ? null : request.getS().stream().collect(Collectors.joining(",")))
.setParam("metrics", request.getMetrics() == null ? null : request.getMetrics().stream().collect(Collectors.joining(",")))
.setParam("p", request.getP())
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("to", request.getTo()),
SearchHistoryResponse.parser());
}
private List<String> metrics;
private String p;
private String ps;
+ private String pullRequest;
private String to;
/**
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public SearchHistoryRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "2017-10-19 or 2017-10-19T13:00:00+0200"
*/
private String branch;
private String component;
+ private String pullRequest;
/**
* This is part of the internal API.
public String getComponent() {
return component;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ComponentRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
new GetRequest(path("component"))
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
+ .setParam("pullRequest", request.getPullRequest())
.setMediaType(MediaTypes.JSON)
).content();
}
}
/**
- * This is a mandatory parameter.
* Possible values:
* <ul>
* <li>"admin"</li>
.setParam("p", request.getP())
.setParam("project", request.getProject())
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("to", request.getTo()),
SearchResponse.parser());
}
private String p;
private String project;
private String ps;
+ private String pullRequest;
private String to;
/**
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public SearchRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "2017-10-19 or 2017-10-19T13:00:00+0200"
*/
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_branches/delete">Further information about this action online (including a response example)</a>
* @since 6.6
*/
- public String delete(DeleteRequest request) {
- return call(
+ public void delete(DeleteRequest request) {
+ call(
new PostRequest(path("delete"))
.setParam("branch", request.getBranch())
.setParam("project", request.getProject())
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.projectpullrequests;
+
+import java.util.List;
+import javax.annotation.Generated;
+
+/**
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/delete">Further information about this action online (including a response example)</a>
+ * @since 7.1
+ */
+@Generated("sonar-ws-generator")
+public class DeleteRequest {
+
+ private String project;
+ private String pullRequest;
+
+ /**
+ * This is a mandatory parameter.
+ * Example value: "my_project"
+ */
+ public DeleteRequest setProject(String project) {
+ this.project = project;
+ return this;
+ }
+
+ public String getProject() {
+ return project;
+ }
+
+ /**
+ * This is a mandatory parameter.
+ * Example value: "1543"
+ */
+ public DeleteRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.projectpullrequests;
+
+import java.util.List;
+import javax.annotation.Generated;
+
+/**
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/list">Further information about this action online (including a response example)</a>
+ * @since 7.1
+ */
+@Generated("sonar-ws-generator")
+public class ListRequest {
+
+ private String project;
+
+ /**
+ * This is a mandatory parameter.
+ * Example value: "my_project"
+ */
+ public ListRequest setProject(String project) {
+ this.project = project;
+ return this;
+ }
+
+ public String getProject() {
+ return project;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.projectpullrequests;
+
+import java.util.stream.Collectors;
+import javax.annotation.Generated;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsConnector;
+import org.sonarqube.ws.ProjectPullRequests.ListWsResponse;
+
+/**
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests">Further information about this web service online</a>
+ */
+@Generated("sonar-ws-generator")
+public class ProjectPullRequestsService extends BaseService {
+
+ public ProjectPullRequestsService(WsConnector wsConnector) {
+ super(wsConnector, "api/project_pull_requests");
+ }
+
+ /**
+ *
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/delete">Further information about this action online (including a response example)</a>
+ * @since 7.1
+ */
+ public void delete(DeleteRequest request) {
+ call(
+ new PostRequest(path("delete"))
+ .setParam("project", request.getProject())
+ .setParam("pullRequest", request.getPullRequest())
+ .setMediaType(MediaTypes.JSON)
+ ).content();
+ }
+
+ /**
+ *
+ * This is part of the internal API.
+ * This is a GET request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/project_pull_requests/list">Further information about this action online (including a response example)</a>
+ * @since 7.1
+ */
+ public ListWsResponse list(ListRequest request) {
+ return call(
+ new GetRequest(path("list"))
+ .setParam("project", request.getProject()),
+ ListWsResponse.parser());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+@Generated("sonar-ws-generator")
+package org.sonarqube.ws.client.projectpullrequests;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import javax.annotation.Generated;
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.stream.Collectors;
import javax.annotation.Generated;
import org.sonarqube.ws.MediaTypes;
-import org.sonarqube.ws.Qualitygates.CreateConditionResponse;
+import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsConnector;
import org.sonarqube.ws.Qualitygates.CreateResponse;
+import org.sonarqube.ws.Qualitygates.CreateConditionResponse;
import org.sonarqube.ws.Qualitygates.GetByProjectResponse;
import org.sonarqube.ws.Qualitygates.ListWsResponse;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
import org.sonarqube.ws.Qualitygates.SearchResponse;
import org.sonarqube.ws.Qualitygates.ShowWsResponse;
import org.sonarqube.ws.Qualitygates.UpdateConditionResponse;
-import org.sonarqube.ws.client.BaseService;
-import org.sonarqube.ws.client.GetRequest;
-import org.sonarqube.ws.client.PostRequest;
-import org.sonarqube.ws.client.WsConnector;
/**
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/qualitygates">Further information about this web service online</a>
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
*/
package org.sonarqube.ws.client.qualitygates;
+import java.util.List;
import javax.annotation.Generated;
/**
* <li>"remFn"</li>
* <li>"remFnOverloaded"</li>
* <li>"repo"</li>
+ * <li>"scope"</li>
* <li>"severity"</li>
* <li>"status"</li>
* <li>"sysTags"</li>
private String branch;
private String component;
+ private String pullRequest;
/**
* This is part of the internal API.
public String getComponent() {
return component;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ListDefinitionsRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
private String branch;
private String component;
private List<String> keys;
+ private String pullRequest;
/**
* This is part of the internal API.
public List<String> getKeys() {
return keys;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ResetRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
private String component;
private List<String> fieldValues;
private String key;
+ private String pullRequest;
private String value;
private List<String> values;
return key;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public SetRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "git@github.com:SonarSource/sonarqube.git"
*/
return call(
new GetRequest(path("list_definitions"))
.setParam("branch", request.getBranch())
- .setParam("component", request.getComponent()),
+ .setParam("component", request.getComponent())
+ .setParam("pullRequest", request.getPullRequest()),
ListDefinitionsWsResponse.parser());
}
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
.setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(",")))
+ .setParam("pullRequest", request.getPullRequest())
.setMediaType(MediaTypes.JSON)
).content();
}
.setParam("component", request.getComponent())
.setParam("fieldValues", request.getFieldValues() == null ? null : request.getFieldValues())
.setParam("key", request.getKey())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("value", request.getValue())
.setParam("values", request.getValues() == null ? null : request.getValues())
.setMediaType(MediaTypes.JSON)
new GetRequest(path("values"))
.setParam("branch", request.getBranch())
.setParam("component", request.getComponent())
- .setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(","))),
+ .setParam("keys", request.getKeys() == null ? null : request.getKeys().stream().collect(Collectors.joining(",")))
+ .setParam("pullRequest", request.getPullRequest()),
ValuesWsResponse.parser());
}
}
private String branch;
private String component;
private List<String> keys;
+ private String pullRequest;
/**
* This is part of the internal API.
public List<String> getKeys() {
return keys;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ValuesRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
private String branch;
private String from;
private String key;
+ private String pullRequest;
private String to;
private String uuid;
return key;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public LinesRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "20"
*/
private String branch;
private String key;
+ private String pullRequest;
/**
* This is part of the internal API.
public String getKey() {
return key;
}
+
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public RawRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
}
.setParam("branch", request.getBranch())
.setParam("from", request.getFrom())
.setParam("key", request.getKey())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("to", request.getTo())
.setParam("uuid", request.getUuid())
.setMediaType(MediaTypes.JSON)
new GetRequest(path("raw"))
.setParam("branch", request.getBranch())
.setParam("key", request.getKey())
+ .setParam("pullRequest", request.getPullRequest())
.setMediaType(MediaTypes.JSON)
).content();
}
private String branch;
private String p;
private String ps;
+ private String pullRequest;
private String sourceFileId;
private String sourceFileKey;
private String sourceFileLineNumber;
return ps;
}
+ /**
+ * This is part of the internal API.
+ * Example value: "5461"
+ */
+ public ListRequest setPullRequest(String pullRequest) {
+ this.pullRequest = pullRequest;
+ return this;
+ }
+
+ public String getPullRequest() {
+ return pullRequest;
+ }
+
/**
* Example value: "AU-TpxcA-iU5OvuD2FL0"
*/
.setParam("branch", request.getBranch())
.setParam("p", request.getP())
.setParam("ps", request.getPs())
+ .setParam("pullRequest", request.getPullRequest())
.setParam("sourceFileId", request.getSourceFileId())
.setParam("sourceFileKey", request.getSourceFileKey())
.setParam("sourceFileLineNumber", request.getSourceFileLineNumber())
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.users;
+
+import java.util.List;
+import javax.annotation.Generated;
+
+/**
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_homepage">Further information about this action online (including a response example)</a>
+ * @since 7.0
+ */
+@Generated("sonar-ws-generator")
+public class SetHomepageRequest {
+
+ private String parameter;
+ private String type;
+
+ /**
+ * Example value: "my_project"
+ */
+ public SetHomepageRequest setParameter(String parameter) {
+ this.parameter = parameter;
+ return this;
+ }
+
+ public String getParameter() {
+ return parameter;
+ }
+
+ /**
+ * This is a mandatory parameter.
+ * Possible values:
+ * <ul>
+ * <li>"PROJECT"</li>
+ * <li>"ORGANIZATION"</li>
+ * <li>"MY_PROJECTS"</li>
+ * <li>"MY_ISSUES"</li>
+ * </ul>
+ */
+ public SetHomepageRequest setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
SearchWsResponse.parser());
}
+ /**
+ *
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_homepage">Further information about this action online (including a response example)</a>
+ * @since 7.0
+ */
+ public void setHomepage(SetHomepageRequest request) {
+ call(
+ new PostRequest(path("set_homepage"))
+ .setParam("parameter", request.getParameter())
+ .setParam("type", request.getType())
+ .setMediaType(MediaTypes.JSON)
+ ).content();
+ }
+
/**
*
* This is part of the internal API.
optional string branch = 21;
optional sonarqube.ws.commons.BranchType branchType = 22;
optional string errorType = 23;
+ optional string pullRequest = 24;
+ optional string pullRequestTitle = 25;
}
enum TaskStatus {
LONG = 1;
SHORT = 2;
+ PULL_REQUEST = 3;
}
optional string project = 17;
optional string branch = 18;
optional string version = 19;
+ optional string pullRequest = 20;
message Tags {
repeated string tags = 1;
string subProjectUuid = 8;
string subProjectName = 9;
string branch = 10;
+ string pullRequest = 11;
}
optional string organization = 29;
optional string branch = 30;
+ optional string pullRequest = 32;
}
message Transitions {
optional int64 unusedProjectId = 9;
optional int64 unusedSubProjectId = 10;
optional string branch = 12;
+ optional string pullRequest = 13;
}
// Response of GET api/issues/changelog
optional string language = 10;
repeated Measure measures = 11;
optional string branch = 12;
+ optional string pullRequest = 13;
}
message Period {
optional Status status = 5;
optional bool isOrphan = 6;
optional string analysisDate = 7;
+}
- message Status {
- // Quality gate status is only present for long living branch
- optional string qualityGateStatus = 1;
- // Merge bugs, vulnerabilities and codeSmell are only present for short living branch
- optional int64 bugs = 2;
- optional int64 vulnerabilities = 3;
- optional int64 codeSmells = 4;
- }
-
+message Status {
+ // Quality gate status is only present for long living branch
+ optional string qualityGateStatus = 1;
+ // Merge bugs, vulnerabilities and codeSmell are only present for short living branch
+ optional int64 bugs = 2;
+ optional int64 vulnerabilities = 3;
+ optional int64 codeSmells = 4;
}
--- /dev/null
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 SonarSource
+// mailto:contact AT sonarsource DOT com
+//
+// SonarQube is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// SonarQube is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+syntax = "proto2";
+
+package sonarqube.ws.projectpullrequest;
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "ProjectPullRequests";
+option optimize_for = SPEED;
+
+import "ws-commons.proto";
+
+// WS api/project_pull_requests/list
+message ListWsResponse {
+ repeated PullRequest pullRequests = 1;
+}
+
+message PullRequest {
+ optional string key = 1;
+ optional string title = 2;
+ optional string branch = 3;
+ optional string base = 4;
+ optional Status status = 5;
+ optional bool isOrphan = 6;
+ optional string analysisDate = 7;
+ optional string url = 8;
+}
+
+message Status {
+ optional int64 bugs = 2;
+ optional int64 vulnerabilities = 3;
+ optional int64 codeSmells = 4;
+}
optional string longName = 3;
optional int32 coveredLines = 4;
optional string branch = 5;
+ optional string pullRequest = 6;
}
}
optional string message = 9;
optional string stacktrace = 10;
optional string fileBranch = 11;
+ optional string filePullRequest = 12;
}
enum TestStatus {
.setName(name)
.setEmail(email)
.setPassword("xxxxxxx")
- .setScmAccounts(asList(scmAccounts)));
+ .setScmAccount(asList(scmAccounts)));
}
private static void deleteAllUsers() {
// Create a user and register her to receive notification on NewIssues
tester.users().generate(t -> t.setLogin(USER_LOGIN).setPassword(USER_PASSWORD).setEmail(USER_EMAIL)
- .setScmAccounts(ImmutableList.of("jhenry")));
+ .setScmAccount(ImmutableList.of("jhenry")));
// Add notifications to the test user
WsClient wsClient = newUserWsClient(ORCHESTRATOR, USER_LOGIN, USER_PASSWORD);
wsClient.wsConnector().call(new PostRequest("api/notifications/add")
.setLogin("test")
.setName("Test")
.setEmail("test@email.com")
- .setScmAccounts(asList("test1", "test2"))
+ .setScmAccount(asList("test1", "test2"))
.setPassword("password"));
assertThat(checkAuthenticationWithAuthenticateWebService("test", "password")).isTrue();
#
function configureTravis {
mkdir -p ~/.local
- curl -sSL https://github.com/SonarSource/travis-utils/tarball/v41 | tar zx --strip-components 1 -C ~/.local
+ curl -sSL https://github.com/SonarSource/travis-utils/tarball/v47 | tar zx --strip-components 1 -C ~/.local
source ~/.local/bin/install
}
configureTravis