diff options
16 files changed, 127 insertions, 53 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java b/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java index f6c6bcd977b..e9058d626b4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/core/issue/ShortBranchIssue.java @@ -19,6 +19,7 @@ */ package org.sonar.core.issue; +import java.util.Date; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -34,8 +35,9 @@ public class ShortBranchIssue implements Trackable { private final RuleKey ruleKey; private final String status; private final String branchName; + private final Date creationDate; - public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName) { + public ShortBranchIssue(String key, @Nullable Integer line, String message, @Nullable String lineHash, RuleKey ruleKey, String status, String branchName, Date creationDate) { this.key = key; this.line = line; this.message = message; @@ -43,6 +45,7 @@ public class ShortBranchIssue implements Trackable { this.ruleKey = ruleKey; this.status = status; this.branchName = branchName; + this.creationDate = creationDate; } public String getKey() { @@ -81,6 +84,11 @@ public class ShortBranchIssue implements Trackable { } @Override + public Date getCreationDate() { + return creationDate; + } + + @Override public int hashCode() { return key.hashCode(); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java index 164f56d5b35..ab8ccd3871f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java @@ -103,8 +103,8 @@ public class IssueDao implements Dao { mapper(dbSession).scrollNonClosedByModuleOrProject(module.projectUuid(), likeModuleUuidPath, handler); } - public List<ShortBranchIssueDto> selectResolvedOrConfirmedByComponentUuids(DbSession dbSession, Collection<String> componentUuids) { - return executeLargeInputs(componentUuids, mapper(dbSession)::selectResolvedOrConfirmedByComponentUuids); + public List<ShortBranchIssueDto> selectOpenByComponentUuids(DbSession dbSession, Collection<String> componentUuids) { + return executeLargeInputs(componentUuids, mapper(dbSession)::selectOpenByComponentUuids); } public void insert(DbSession session, IssueDto dto) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java index 4335a471126..75ec4c9d542 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java @@ -32,7 +32,7 @@ public interface IssueMapper { List<IssueDto> selectByKeys(List<String> keys); - List<ShortBranchIssueDto> selectResolvedOrConfirmedByComponentUuids(List<String> componentUuids); + List<ShortBranchIssueDto> selectOpenByComponentUuids(List<String> componentUuids); void insert(IssueDto issue); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java index 3c1901041e4..79208ddd20d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ShortBranchIssueDto.java @@ -27,6 +27,8 @@ import org.apache.commons.lang.builder.ToStringStyle; import org.sonar.api.rule.RuleKey; import org.sonar.core.issue.ShortBranchIssue; +import static org.sonar.api.utils.DateUtils.longToDate; + public final class ShortBranchIssueDto implements Serializable { private String kee; @@ -34,6 +36,7 @@ public final class ShortBranchIssueDto implements Serializable { private Integer line; private String checksum; private String status; + private Long issueCreationDate; // joins private String ruleKey; @@ -114,7 +117,7 @@ public final class ShortBranchIssueDto implements Serializable { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } - public static ShortBranchIssue toShortBranchIssue(ShortBranchIssueDto dto) { - return new ShortBranchIssue(dto.getKey(), dto.getLine(), dto.getMessage(), dto.getChecksum(), dto.getRuleKey(), dto.getStatus(), dto.getBranchName()); + public ShortBranchIssue toShortBranchIssue() { + return new ShortBranchIssue(getKey(), getLine(), getMessage(), getChecksum(), getRuleKey(), getStatus(), getBranchName(), longToDate(issueCreationDate)); } } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index 0a4b557880c..5d7e051032f 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -665,7 +665,7 @@ ON i.project_uuid = b.uuid AND b.branch_type = 'SHORT' AND b.merge_branch_uuid = #{mergeBranchUuid,jdbcType=VARCHAR} - AND (i.status = 'CONFIRMED' OR i.status = 'RESOLVED') + AND i.status != 'CLOSED' </select> </mapper> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml index 09d43435502..545963ea5e5 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml @@ -213,13 +213,14 @@ </foreach> </select> - <select id="selectResolvedOrConfirmedByComponentUuids" parameterType="map" resultType="ShortBranchIssue"> + <select id="selectOpenByComponentUuids" parameterType="map" resultType="ShortBranchIssue"> select i.kee as kee, i.message as message, i.line as line, i.status as status, i.checksum as checksum, + i.issue_creation_date as issueCreationDate, r.plugin_rule_key as ruleKey, r.plugin_name as ruleRepo, b.kee as branchName @@ -230,7 +231,7 @@ <foreach collection="list" open="(" close=")" item="key" separator=","> #{key,jdbcType=VARCHAR} </foreach> - and (i.status = 'CONFIRMED' OR i.status = 'RESOLVED') + and i.status <> 'CLOSED' </select> <select id="scrollNonClosedByModuleOrProject" parameterType="map" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY"> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java index 8c4682215c1..cc31fa3d9c7 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java @@ -184,7 +184,7 @@ public class IssueDaoTest { } @Test - public void selectResolvedOrConfirmedByComponentUuid() { + public void selectOpenByComponentUuid() { RuleDefinitionDto rule = db.rules().insert(); ComponentDto project = db.components().insertMainBranch(); ComponentDto projectBranch = db.components().insertProjectBranch(project, @@ -200,13 +200,13 @@ public class IssueDaoTest { 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)); - assertThat(underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid()))) + assertThat(underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid()))) .extracting("kee") - .containsOnly(confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey()); + .containsOnly(openIssue.getKey(), reopenedIssue.getKey(), confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey()); } @Test - public void selectResolvedOrConfirmedByComponentUuid_should_correctly_map_required_fields() { + public void selectOpenByComponentUuid_should_correctly_map_required_fields() { RuleDefinitionDto rule = db.rules().insert(); ComponentDto project = db.components().insertMainBranch(); ComponentDto projectBranch = db.components().insertProjectBranch(project, @@ -216,7 +216,7 @@ public class IssueDaoTest { ComponentDto file = db.components().insertComponent(newFileDto(projectBranch)); IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE")); - ShortBranchIssueDto fp = underTest.selectResolvedOrConfirmedByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0); + ShortBranchIssueDto fp = underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0); assertThat(fp.getLine()).isEqualTo(fpIssue.getLine()); assertThat(fp.getMessage()).isEqualTo(fpIssue.getMessage()); assertThat(fp.getChecksum()).isEqualTo(fpIssue.getChecksum()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java index 1beca5a1b64..cf91a53b07c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssuesLoader.java @@ -56,7 +56,7 @@ public class ShortBranchIssuesLoader { return Collections.emptyList(); } try (DbSession session = dbClient.openSession(false)) { - return dbClient.issueDao().selectResolvedOrConfirmedByComponentUuids(session, uuids) + return dbClient.issueDao().selectOpenByComponentUuids(session, uuids) .stream() .map(ShortBranchIssueDto::toShortBranchIssue) .collect(Collectors.toList()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java index d9fca55cbca..41f1dd43c09 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/component/ShortBranchComponentsWithIssuesTest.java @@ -111,7 +111,7 @@ public class ShortBranchComponentsWithIssuesTest { setRootUuid(long1.uuid()); assertThat(underTest.getUuids(fileWithNoIssues.getKey())).isEmpty(); - assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).isEmpty(); + assertThat(underTest.getUuids(fileWithOneOpenIssue.getKey())).containsOnly(fileWithOneOpenIssue.uuid()); assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).containsOnly(fileWithOneResolvedIssue.uuid()); assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssues.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssues.uuid()); @@ -128,7 +128,7 @@ public class ShortBranchComponentsWithIssuesTest { assertThat(underTest.getUuids(fileWithOneResolvedIssue.getKey())).isEmpty(); assertThat(underTest.getUuids(fileWithOneResolvedIssueOnLong2.getKey())).containsOnly(fileWithOneResolvedIssueOnLong2.uuid()); - assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).isEmpty(); + assertThat(underTest.getUuids(fileWithOneOpenIssueOnLong2.getKey())).containsOnly(fileWithOneOpenIssueOnLong2.uuid()); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java index 134f83b9d4e..c3b720e872c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ShortBranchIssueMergerTest.java @@ -20,9 +20,12 @@ package org.sonar.server.computation.task.projectanalysis.issue; import com.google.common.collect.ImmutableMap; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; @@ -62,7 +65,7 @@ public class ShortBranchIssueMergerTest { @Test public void do_nothing_if_no_match() { when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.emptyList()); - DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null); + DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date()); copier.tryMerge(component, Collections.singleton(i)); verify(resolvedShortBranchIssuesLoader).loadCandidateIssuesForMergingInTargetBranch(component); @@ -71,7 +74,7 @@ public class ShortBranchIssueMergerTest { @Test public void do_nothing_if_no_new_issue() { - DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null); + DefaultIssue i = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date()); when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(newShortBranchIssue(i, "myBranch"))); copier.tryMerge(component, Collections.emptyList()); @@ -81,9 +84,9 @@ public class ShortBranchIssueMergerTest { @Test public void update_status_on_matches() { - DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null); + DefaultIssue issue1 = createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null, new Date()); ShortBranchIssue shortBranchIssue = newShortBranchIssue(issue1, "myBranch"); - DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null); + DefaultIssue newIssue = createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, new Date()); when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Collections.singleton(shortBranchIssue)); when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue, issue1)); @@ -96,11 +99,11 @@ public class ShortBranchIssueMergerTest { @Test public void prefer_resolved_issues() { - ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch1"); - ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null), "myBranch2"); - DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE); + ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, new Date()), "myBranch1"); + ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_CONFIRMED, null, new Date()), "myBranch2"); + DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE, new Date()); ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3"); - DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null); + DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date()); when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3)); when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3)); @@ -108,7 +111,36 @@ public class ShortBranchIssueMergerTest { verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3"); } - private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution) { + @Test + public void prefer_confirmed_issues() { + ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, new Date()), "myBranch1"); + ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, new Date()), "myBranch2"); + DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_CONFIRMED, null, new Date()); + ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3"); + DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date()); + + when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3)); + when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3)); + copier.tryMerge(component, Collections.singleton(newIssue)); + verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3"); + } + + @Test + public void prefer_older_issues() { + Instant now = Instant.now(); + ShortBranchIssue shortBranchIssue1 = newShortBranchIssue(createIssue("issue1", "rule1", Issue.STATUS_REOPENED, null, Date.from(now.plus(2, ChronoUnit.SECONDS))), "myBranch1"); + ShortBranchIssue shortBranchIssue2 = newShortBranchIssue(createIssue("issue2", "rule1", Issue.STATUS_OPEN, null, Date.from(now.plus(1, ChronoUnit.SECONDS))), "myBranch2"); + DefaultIssue issue3 = createIssue("issue3", "rule1", Issue.STATUS_OPEN, null, Date.from(now)); + ShortBranchIssue shortBranchIssue3 = newShortBranchIssue(issue3, "myBranch3"); + DefaultIssue newIssue = createIssue("newIssue", "rule1", Issue.STATUS_OPEN, null, new Date()); + + when(resolvedShortBranchIssuesLoader.loadCandidateIssuesForMergingInTargetBranch(component)).thenReturn(Arrays.asList(shortBranchIssue1, shortBranchIssue2, shortBranchIssue3)); + when(resolvedShortBranchIssuesLoader.loadDefaultIssuesWithChanges(anyListOf(ShortBranchIssue.class))).thenReturn(ImmutableMap.of(shortBranchIssue3, issue3)); + copier.tryMerge(component, Collections.singleton(newIssue)); + verify(issueLifecycle).mergeConfirmedOrResolvedFromShortLivingBranch(newIssue, issue3, "myBranch3"); + } + + private static DefaultIssue createIssue(String key, String ruleKey, String status, @Nullable String resolution, Date creationDate) { DefaultIssue issue = new DefaultIssue(); issue.setKey(key); issue.setRuleKey(RuleKey.of("repo", ruleKey)); @@ -116,10 +148,11 @@ public class ShortBranchIssueMergerTest { issue.setLine(1); issue.setStatus(status); issue.setResolution(resolution); + issue.setCreationDate(creationDate); return issue; } private ShortBranchIssue newShortBranchIssue(DefaultIssue i, String originBranch) { - return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), originBranch); + return new ShortBranchIssue(i.key(), i.line(), i.message(), i.getLineHash(), i.ruleKey(), i.status(), originBranch, i.creationDate()); } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java index 02032a1c62d..c7b0626e826 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java @@ -653,4 +653,9 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. public String getStatus() { return status; } + + @Override + public Date getCreationDate() { + return creationDate; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java index 69096993324..60cc9b05782 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java @@ -29,13 +29,11 @@ import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.Issue; import org.sonar.api.rule.RuleKey; +import static java.util.Comparator.comparing; + public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory) { - match(tracking, searchKeyFactory, false); - } - - protected void match(Tracking<RAW, BASE> tracking, Function<Trackable, SearchKey> searchKeyFactory, boolean preferResolved) { if (tracking.isComplete()) { return; @@ -49,21 +47,25 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { for (RAW raw : tracking.getUnmatchedRaws()) { SearchKey rawKey = searchKeyFactory.apply(raw); Collection<BASE> bases = baseSearch.get(rawKey); - if (!bases.isEmpty()) { - BASE match; - if (preferResolved) { - match = bases.stream() - .filter(i -> Issue.STATUS_RESOLVED.equals(i.getStatus())) - .findFirst() - .orElse(bases.iterator().next()); - } else { - // TODO taking the first one. Could be improved if there are more than 2 issues on the same line. - // Message could be checked to take the best one. - match = bases.iterator().next(); - } - tracking.match(raw, match); - baseSearch.remove(rawKey, match); - } + bases.stream() + .sorted(comparing(this::statusRank).reversed() + .thenComparing(comparing(Trackable::getCreationDate))) + .findFirst() + .ifPresent(match -> { + tracking.match(raw, match); + baseSearch.remove(rawKey, match); + }); + } + } + + private int statusRank(BASE i) { + switch (i.getStatus()) { + case Issue.STATUS_RESOLVED: + return 2; + case Issue.STATUS_CONFIRMED: + return 1; + default: + return 0; } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java index 54fd8ea2bba..b5fb56a6f26 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/SimpleTracker.java @@ -31,10 +31,10 @@ public class SimpleTracker<RAW extends Trackable, BASE extends Trackable> extend Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput); // 1. match issues with same rule, same line and same line hash, but not necessarily with same message - match(tracking, LineAndLineHashKey::new, true); + match(tracking, LineAndLineHashKey::new); // 2. match issues with same rule, same message and same line hash - match(tracking, LineHashAndMessageKey::new, true); + match(tracking, LineHashAndMessageKey::new); return tracking; } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java index 0d04b81e366..6c6af607afc 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/Trackable.java @@ -19,8 +19,10 @@ */ package org.sonar.core.issue.tracking; +import java.util.Date; import javax.annotation.CheckForNull; import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.DefaultIssue; public interface Trackable { @@ -42,4 +44,9 @@ public interface Trackable { RuleKey getRuleKey(); String getStatus(); + + /** + * Functional creation date for the issue. See {@link DefaultIssue#creationDate()} + */ + Date getCreationDate(); } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java index 59e106f1433..c894c65fcdb 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java @@ -21,6 +21,7 @@ package org.sonar.core.issue.tracking; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; import javax.annotation.Nullable; import org.apache.commons.codec.digest.DigestUtils; @@ -186,7 +187,7 @@ public class TrackerTest { @Test public void do_not_fail_if_raw_line_does_not_exist() { FakeInput baseInput = new FakeInput(); - FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg", org.sonar.api.issue.Issue.STATUS_OPEN)); + FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg", org.sonar.api.issue.Issue.STATUS_OPEN, new Date())); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); @@ -386,12 +387,14 @@ public class TrackerTest { private final Integer line; private final String message, lineHash; private final String status; + private final Date creationDate; - Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status) { + Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date creationDate) { this.line = line; this.lineHash = lineHash; this.ruleKey = ruleKey; this.status = status; + this.creationDate = creationDate; this.message = trim(message); } @@ -419,6 +422,11 @@ public class TrackerTest { public String getStatus() { return status; } + + @Override + public Date getCreationDate() { + return creationDate; + } } private static class FakeInput implements Input<Issue> { @@ -438,7 +446,7 @@ public class TrackerTest { } Issue createIssueOnLine(int line, RuleKey ruleKey, String message) { - Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN); + Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date()); issues.add(issue); return issue; } @@ -447,7 +455,7 @@ public class TrackerTest { * No line (line 0) */ Issue createIssue(RuleKey ruleKey, String message) { - Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN); + Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date()); issues.add(issue); return issue; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java index a0888d20341..2ddba9e2015 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueFromWs.java @@ -19,8 +19,10 @@ */ package org.sonar.scanner.issue.tracking; +import java.util.Date; import javax.annotation.CheckForNull; import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.DateUtils; import org.sonar.core.issue.tracking.Trackable; import static org.apache.commons.lang.StringUtils.trim; @@ -65,7 +67,12 @@ public class ServerIssueFromWs implements Trackable { @Override public String getStatus() { - throw new UnsupportedOperationException(); + return dto.getStatus(); + } + + @Override + public Date getCreationDate() { + return DateUtils.longToDate(dto.getCreationDate()); } } |