public class IssueQueryParams {
- private final String projectUuid;
- private final String branchName;
+ private final String branchUuid;
private final List<String> languages;
private final List<String> ruleRepositories;
private final boolean resolvedOnly;
private final Long changedSince;
- public IssueQueryParams(String projectUuid, String branchName, @Nullable List<String> languages,
+ public IssueQueryParams(String branchUuid, @Nullable List<String> languages,
@Nullable List<String> ruleRepositories, boolean resolvedOnly, @Nullable Long changedSince) {
- this.projectUuid = projectUuid;
- this.branchName = branchName;
+ this.branchUuid = branchUuid;
this.languages = languages;
this.ruleRepositories = ruleRepositories;
this.resolvedOnly = resolvedOnly;
this.changedSince = changedSince;
}
- public String getProjectUuid() {
- return projectUuid;
- }
-
- public String getBranchName() {
- return branchName;
+ public String getBranchUuid() {
+ return branchUuid;
}
@CheckForNull
<include refid="selectByBranchColumns"/>
, p.path as filePath
from issues i
- inner join project_branches b on i.project_uuid = b.project_uuid
inner join rules r on r.uuid = i.rule_uuid
inner join components p on p.uuid=i.component_uuid
where
- b.kee = #{queryParams.branchName}
- AND i.project_uuid = #{queryParams.projectUuid}
+ i.project_uuid = #{queryParams.branchUuid}
<if test="queryParams.changedSince != null">
AND i.issue_update_date >= #{queryParams.changedSince,jdbcType=BIGINT}
</if>
row_number() over(order by i.kee ASC) as row_number,
<include refid="selectByBranchColumns"/>
from issues i
- inner join project_branches b on i.project_uuid = b.project_uuid
inner join rules r on r.uuid = i.rule_uuid
where
- b.kee = #{queryParams.branchName}
- AND i.project_uuid = #{queryParams.projectUuid}
+ i.project_uuid = #{queryParams.branchUuid}
<if test="queryParams.changedSince != null">
AND i.issue_update_date >= #{queryParams.changedSince,jdbcType=BIGINT}
</if>
select
<include refid="selectByBranchColumns"/>
from issues i
- inner join project_branches b on i.project_uuid = b.project_uuid
inner join rules r on r.uuid = i.rule_uuid
where
- b.kee = #{queryParams.branchName}
- AND i.project_uuid = #{queryParams.projectUuid}
+ i.project_uuid = #{queryParams.branchUuid}
<if test="queryParams.changedSince != null">
AND i.issue_update_date >= #{queryParams.changedSince,jdbcType=BIGINT}
</if>
<select id="selectRecentlyClosedIssues" resultType="string">
select i.kee
from issues i
- inner join project_branches b on i.project_uuid = b.project_uuid
inner join rules r on r.uuid = i.rule_uuid
where
- i.project_uuid = #{queryParams.projectUuid}
+ i.project_uuid = #{queryParams.branchUuid}
AND issue_update_date >= #{queryParams.changedSince}
- AND b.kee = #{queryParams.branchName}
<if test="queryParams.ruleRepositories != null">
AND r.plugin_name IN
<foreach item="ruleRepository" index="index" collection="queryParams.ruleRepositories" open="(" separator="," close=")">
import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import static org.apache.commons.lang.math.RandomUtils.nextInt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
import static org.junit.Assert.assertFalse;
+import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
+import static org.sonar.api.issue.Issue.STATUSES;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_REOPENED;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
- private IssueDao underTest = db.getDbClient().issueDao();
+ private final IssueDao underTest = db.getDbClient().issueDao();
@Test
public void selectByKeyOrFail() {
assertThat(issues).containsOnly("I1", "I2");
}
+ @Test
+ public void selectByBranch() {
+ long updatedAt = 1_340_000_000_000L;
+ long changedSince = 1_000_000_000_000L;
+
+ ComponentDto project = db.components().insertPrivateProject();
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java"));
+
+ ComponentDto branchA = db.components().insertProjectBranch(project, b -> b.setKey("branchA"));
+ ComponentDto fileA = db.components().insertComponent(newFileDto(branchA));
+
+ List<String> statusesA = List.of(STATUS_OPEN, STATUS_REVIEWED, STATUS_CLOSED, STATUS_RESOLVED);
+ IntStream.range(0, statusesA.size()).forEach(i -> insertBranchIssue(branchA, fileA, rule, "A" + i, statusesA.get(i), updatedAt));
+
+ ComponentDto branchB = db.components().insertProjectBranch(project, b -> b.setKey("branchB"));
+ ComponentDto fileB = db.components().insertComponent(newFileDto(branchB));
+
+ List<String> statusesB = List.of(STATUS_OPEN, STATUS_RESOLVED);
+ IntStream.range(0, statusesB.size()).forEach(i -> insertBranchIssue(branchB, fileB, rule, "B" + i, statusesB.get(i), updatedAt));
+
+ List<IssueDto> branchAIssuesA1 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchA, "java", false, changedSince), 1);
+
+ assertThat(branchAIssuesA1)
+ .extracting(IssueDto::getKey, IssueDto::getStatus)
+ .containsExactlyInAnyOrder(
+ tuple("issueA0", STATUS_OPEN),
+ tuple("issueA1", STATUS_REVIEWED),
+ tuple("issueA3", STATUS_RESOLVED)
+ );
+
+ List<IssueDto> branchAIssuesA2 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchA, "java", true, changedSince), 1);
+
+ assertThat(branchAIssuesA2)
+ .extracting(IssueDto::getKey, IssueDto::getStatus)
+ .containsExactly(tuple("issueA3", STATUS_RESOLVED));
+
+ List<IssueDto> branchBIssuesB1 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchB, "java", false, changedSince), 1);
+
+ assertThat(branchBIssuesB1)
+ .extracting(IssueDto::getKey, IssueDto::getStatus)
+ .containsExactlyInAnyOrder(
+ tuple("issueB0", STATUS_OPEN),
+ tuple("issueB1", STATUS_RESOLVED)
+ );
+
+ List<IssueDto> branchBIssuesB2 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchB, "java", true, changedSince), 1);
+
+ assertThat(branchBIssuesB2)
+ .extracting(IssueDto::getKey, IssueDto::getStatus)
+ .containsExactly(tuple("issueB1", STATUS_RESOLVED));
+ }
+
@Test
public void selectByComponentUuidPaginated() {
// contains I1 and I2
ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
- IssueDto openIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_OPEN).setResolution(null));
- IssueDto closedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED));
- IssueDto reopenedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_REOPENED).setResolution(null));
- IssueDto confirmedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_CONFIRMED).setResolution(null));
- IssueDto wontfixIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX));
- IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE));
+ IssueDto openIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_OPEN).setResolution(null));
+ IssueDto closedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_FIXED));
+ IssueDto reopenedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_REOPENED).setResolution(null));
+ IssueDto confirmedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_CONFIRMED).setResolution(null));
+ IssueDto wontfixIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
+ IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE));
assertThat(underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
.extracting("kee")
.forEach(moduleOrDir -> {
String projectUuid = moduleOrDir.projectUuid();
// CLOSED issue => not returned
- db.issues().insertIssue(t -> t.setProjectUuid(projectUuid).setComponent(moduleOrDir).setStatus(Issue.STATUS_CLOSED));
+ db.issues().insertIssue(t -> t.setProjectUuid(projectUuid).setComponent(moduleOrDir).setStatus(STATUS_CLOSED));
assertThat(underTest.selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(db.getSession(), projectUuid))
.isEmpty();
// status != CLOSED => returned
- Issue.STATUSES.stream()
- .filter(t -> !Issue.STATUS_CLOSED.equals(t))
+ STATUSES.stream()
+ .filter(t -> !STATUS_CLOSED.equals(t))
.forEach(status -> {
IssueDto issue = db.issues().insertIssue(t -> t.setProjectUuid(projectUuid).setComponent(moduleOrDir).setStatus(status));
assertThat(underTest.selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(db.getSession(), projectUuid))
Stream.of(project1, file11, application, view, subview, project2, file21)
.forEach(neitherModuleNorDir -> {
String projectUuid = neitherModuleNorDir.projectUuid();
- Issue.STATUSES
+ STATUSES
.forEach(status -> {
db.issues().insertIssue(t -> t.setProjectUuid(projectUuid).setComponent(neitherModuleNorDir).setStatus(status));
assertThat(underTest.selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(db.getSession(), projectUuid))
String projectUuid = component.projectUuid();
// issues for each status => returned if component is dir or module
- Issue.STATUSES
+ STATUSES
.forEach(status -> db.issues().insertIssue(t -> t.setProjectUuid(projectUuid).setComponent(component).setStatus(status)));
if (allModuleOrDirs.contains(component)) {
assertThat(underTest.selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(db.getSession(), projectUuid))
private static RuleType randomRuleTypeExceptHotspot() {
return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
}
+
+ private void insertBranchIssue(ComponentDto branch, ComponentDto file, RuleDto rule, String id, String status, Long updateAt) {
+ db.issues().insert(rule, branch, file, i -> i.setKee("issue" + id).setStatus(status).setUpdatedAt(updateAt).setType(randomRuleTypeExceptHotspot()));
+ }
+
+ private static IssueQueryParams buildSelectByBranchQuery(ComponentDto branch, String language, boolean resolvedOnly, Long changedSince) {
+ return new IssueQueryParams(branch.uuid(), List.of(language), List.of(language), resolvedOnly, changedSince);
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.issue;
+
+import java.util.List;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueQueryParamsTest {
+ private final List<String> languages = List.of("java");
+ private final List<String> ruleRepositories = List.of("js-security", "java");
+
+ @Test
+ public void validate_issue_query_parameters_structure() {
+ boolean resolvedOnly = false;
+ long changedSince = 1_000_000L;
+ String branchUuid = "master-branch-uuid";
+
+ IssueQueryParams queryParameters = new IssueQueryParams(branchUuid, languages, ruleRepositories, resolvedOnly, changedSince);
+
+ assertThat(queryParameters.getBranchUuid()).isEqualTo(branchUuid);
+ assertThat(queryParameters.getLanguages()).isEqualTo(languages);
+ assertThat(queryParameters.getRuleRepositories()).isEqualTo(ruleRepositories);
+ assertThat(queryParameters.isResolvedOnly()).isFalse();
+ assertThat(queryParameters.getChangedSince()).isEqualTo(changedSince);
+
+ }
+}
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.issue.IssueQueryParams;
import org.sonar.db.project.ProjectDto;
+import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.ws.pull.PullActionIssuesRetriever;
import org.sonar.server.issue.ws.pull.PullActionResponseWriter;
import org.sonar.server.user.UserSession;
-import static java.util.Optional.*;
+import static java.util.Optional.ofNullable;
import static org.sonar.api.web.UserRole.USER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_PULL;
private final DbClient dbClient;
private final UserSession userSession;
private final PullActionResponseWriter pullActionResponseWriter;
+ private final ComponentFinder componentFinder;
- public PullAction(DbClient dbClient, UserSession userSession, PullActionResponseWriter pullActionResponseWriter) {
+ public PullAction(DbClient dbClient, UserSession userSession, PullActionResponseWriter pullActionResponseWriter, ComponentFinder componentFinder) {
this.dbClient = dbClient;
this.userSession = userSession;
this.pullActionResponseWriter = pullActionResponseWriter;
+ this.componentFinder = componentFinder;
}
@Override
throws IOException {
try (DbSession dbSession = dbClient.openSession(false)) {
- Optional<ProjectDto> projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projectKey);
- validateProjectPermissions(projectDto);
+ ProjectDto projectDto = componentFinder.getProjectByKey(dbSession, projectKey);
+ userSession.checkProjectPermission(USER, projectDto);
+ BranchDto branchDto = componentFinder.getBranchOrPullRequest(dbSession, projectDto, branchName, null);
pullActionResponseWriter.appendTimestampToResponse(outputStream);
- var pullActionQueryParams = new IssueQueryParams(projectDto.get().getUuid(), branchName,
- languages, ruleRepositories, resolvedOnly, changedSince);
- retrieveAndSendIssues(dbSession, projectDto.get().getUuid(), pullActionQueryParams, outputStream);
+ IssueQueryParams pullActionQueryParams = new IssueQueryParams(branchDto.getUuid(), languages, ruleRepositories, resolvedOnly, changedSince);
+ retrieveAndSendIssues(dbSession, pullActionQueryParams, outputStream);
}
}
- private void validateProjectPermissions(Optional<ProjectDto> projectDto) {
- if (projectDto.isEmpty()) {
- throw new IllegalArgumentException("Invalid " + PROJECT_KEY_PARAM + " parameter");
- }
- userSession.checkProjectPermission(USER, projectDto.get());
- }
-
- private void retrieveAndSendIssues(DbSession dbSession, String componentUuid, IssueQueryParams queryParams, OutputStream outputStream)
+ private void retrieveAndSendIssues(DbSession dbSession, IssueQueryParams queryParams, OutputStream outputStream)
throws IOException {
var issuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
- Set<String> issueKeysSnapshot = new HashSet<>(getIssueKeysSnapshot(componentUuid, queryParams.getChangedSince()));
+ Set<String> issueKeysSnapshot = new HashSet<>(getIssueKeysSnapshot(queryParams.getBranchUuid(), queryParams.getChangedSince()));
Consumer<List<IssueDto>> listConsumer = issueDtos -> pullActionResponseWriter.appendIssuesToResponse(issueDtos, outputStream);
issuesRetriever.processIssuesByBatch(dbSession, issueKeysSnapshot, listConsumer);
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDbTester;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.issue.IssueDbTester;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.protobuf.DbCommons;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.ws.pull.PullActionProtobufObjectGenerator;
import org.sonar.server.issue.ws.pull.PullActionResponseWriter;
import org.sonar.server.tester.UserSessionRule;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Issues;
+import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
private final PullActionProtobufObjectGenerator pullActionProtobufObjectGenerator = new PullActionProtobufObjectGenerator();
private final PullActionResponseWriter pullActionResponseWriter = new PullActionResponseWriter(system2, pullActionProtobufObjectGenerator);
+ private final ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT);
+ private final ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), resourceTypes);
private final IssueDbTester issueDbTester = new IssueDbTester(db);
private final ComponentDbTester componentDbTester = new ComponentDbTester(db);
- private final PullAction underTest = new PullAction(db.getDbClient(), userSession, pullActionResponseWriter);
+ private final PullAction underTest = new PullAction(db.getDbClient(), userSession, pullActionResponseWriter, componentFinder);
private final WsActionTester tester = new WsActionTester(underTest);
private RuleDto correctRule, incorrectRule;
.setParam("branchName", DEFAULT_BRANCH);
assertThatThrownBy(request::execute)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Invalid projectKey parameter");
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Project 'projectKey' not found");
}
@Test
.hasMessage("Insufficient privileges");
}
+ @Test
+ public void givenNotExistingBranchKey_throwException() {
+ DbCommons.TextRange textRange = DbCommons.TextRange.newBuilder()
+ .setStartLine(1)
+ .setEndLine(2)
+ .setStartOffset(3)
+ .setEndOffset(4)
+ .build();
+ DbIssues.Locations.Builder mainLocation = DbIssues.Locations.newBuilder()
+ .setChecksum("hash")
+ .setTextRange(textRange);
+
+ RuleDto rule = db.rules().insertIssueRule(r -> r.setRepositoryKey("java").setRuleKey("S1000"));
+ IssueDto issueDto = issueDbTester.insertIssue(rule, p -> p.setSeverity("MINOR")
+ .setManualSeverity(true)
+ .setMessage("message")
+ .setCreatedAt(NOW)
+ .setStatus(Issue.STATUS_RESOLVED)
+ .setLocations(mainLocation.build())
+ .setType(Common.RuleType.BUG.getNumber()));
+ loginWithBrowsePermission(issueDto);
+
+ TestRequest request = tester.newRequest()
+ .setParam("projectKey", issueDto.getProjectKey())
+ .setParam("branchName", "non-existent-branch");
+
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage(format("Branch 'non-existent-branch' in project '%s' not found", issueDto.getProjectKey()));
+ }
+
@Test
public void givenValidProjectKeyAndOneIssueOnBranch_returnOneIssue() throws IOException {
DbCommons.TextRange textRange = DbCommons.TextRange.newBuilder()
public class PullActionIssuesRetrieverTest {
private final DbClient dbClient = mock(DbClient.class);
- private final String projectUuid = "default-project-uuid";
- private final String branchName = "master";
+ private final String branchUuid = "master-branch-uuid";
private final List<String> languages = List.of("java");
private final List<String> ruleRepositories = List.of("js-security", "java");
private final Long defaultChangedSince = 1_000_000L;
- private final IssueQueryParams queryParams = new IssueQueryParams(projectUuid, branchName, languages, ruleRepositories, false,
- defaultChangedSince);
+ private final IssueQueryParams queryParams = new IssueQueryParams(branchUuid, languages, ruleRepositories, false, defaultChangedSince);
private final IssueDao issueDao = mock(IssueDao.class);
@Before