diff options
author | belen-pruvost-sonarsource <belen.pruvost@sonarsource.com> | 2022-01-21 10:43:10 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-01-21 20:03:22 +0000 |
commit | e2ee8eeba4e3c7b08cd929c56d0fb136d1cdf372 (patch) | |
tree | 08dd8921d0d8714e6593d31832b66e606ade5bfb | |
parent | 9d1361c43487f91b3d001a7e1385d35fa05a5115 (diff) | |
download | sonarqube-e2ee8eeba4e3c7b08cd929c56d0fb136d1cdf372.tar.gz sonarqube-e2ee8eeba4e3c7b08cd929c56d0fb136d1cdf372.zip |
SONAR-14929 - Handle Issue no longer being new on feature branch
23 files changed, 419 insertions, 78 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index ec409d28df6..6928fc4bcd9 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -33,7 +33,6 @@ import org.sonar.ce.task.projectanalysis.component.BranchLoader; import org.sonar.ce.task.projectanalysis.component.BranchPersisterImpl; import org.sonar.ce.task.projectanalysis.component.ConfigurationRepositoryImpl; import org.sonar.ce.task.projectanalysis.component.DisabledComponentsHolderImpl; -import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids; import org.sonar.ce.task.projectanalysis.component.ProjectPersister; import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids; import org.sonar.ce.task.projectanalysis.component.ReportModulesPath; @@ -64,12 +63,14 @@ import org.sonar.ce.task.projectanalysis.issue.IssueAssigner; import org.sonar.ce.task.projectanalysis.issue.IssueCounter; import org.sonar.ce.task.projectanalysis.issue.IssueCreationDateCalculator; import org.sonar.ce.task.projectanalysis.issue.IssueLifecycle; +import org.sonar.ce.task.projectanalysis.issue.IssueOnReferenceBranchVisitor; import org.sonar.ce.task.projectanalysis.issue.IssueTrackingDelegator; import org.sonar.ce.task.projectanalysis.issue.IssueVisitors; import org.sonar.ce.task.projectanalysis.issue.IssuesRepositoryVisitor; import org.sonar.ce.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssuesVisitor; import org.sonar.ce.task.projectanalysis.issue.MovedIssueVisitor; import org.sonar.ce.task.projectanalysis.issue.NewEffortAggregator; +import org.sonar.ce.task.projectanalysis.issue.NewIssueClassifier; import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache; import org.sonar.ce.task.projectanalysis.issue.PullRequestSourceBranchMerger; import org.sonar.ce.task.projectanalysis.issue.PullRequestTrackerExecution; @@ -105,8 +106,8 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryImpl; import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto; import org.sonar.ce.task.projectanalysis.metric.MetricModule; import org.sonar.ce.task.projectanalysis.notification.NotificationFactory; -import org.sonar.ce.task.projectanalysis.issue.NewIssueClassifier; import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver; +import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids; import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl; import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResultTextConverterImpl; import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateHolderImpl; @@ -266,6 +267,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop MovedIssueVisitor.class, IssuesRepositoryVisitor.class, RemoveProcessedComponentsVisitor.class, + IssueOnReferenceBranchVisitor.class, // visitors : order is important, measure computers must be executed at the end in order to access to every measures / issues LoadComponentUuidsHavingOpenIssuesVisitor.class, diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java index 3739b26c6ed..93a0834b4df 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java @@ -127,7 +127,7 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter { private void process(Component component, DefaultIssue issue, CacheAppender<DefaultIssue> cacheAppender) { issueLifecycle.doAutomaticTransition(issue); issueVisitors.onIssue(component, issue); - if (issue.isNew() || issue.isChanged() || issue.isCopied()) { + if (issue.isNew() || issue.isChanged() || issue.isCopied() || issue.isNoLongerNewCodeReferenceIssue()) { cacheAppender.append(issue); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java index 430a1249d64..96b96d38606 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java @@ -216,5 +216,6 @@ public class IssueLifecycle { toIssue.setEffort(debtCalculator.calculate(toIssue)); toIssue.setOnDisabledRule(fromIssue.isOnDisabledRule()); toIssue.setSelectedAt(fromIssue.selectedAt()); + toIssue.setIsNewCodeReferenceIssue(fromIssue.isNewCodeReferenceIssue()); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueOnReferenceBranchVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueOnReferenceBranchVisitor.java new file mode 100644 index 00000000000..e99e008253b --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueOnReferenceBranchVisitor.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.ce.task.projectanalysis.issue; + +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.core.issue.DefaultIssue; + +public class IssueOnReferenceBranchVisitor extends IssueVisitor { + + private final NewIssueClassifier newIssueClassifier; + + public IssueOnReferenceBranchVisitor(NewIssueClassifier newIssueClassifier) { + this.newIssueClassifier = newIssueClassifier; + } + + @Override + public void onIssue(Component component, DefaultIssue issue) { + if (!newIssueClassifier.isEnabled()) { + return; + } + + issue.setIsOnReferencedBranch(newIssueClassifier.isOnBranchUsingReferenceBranch()); + + if (issue.isOnReferencedBranch()) { + issue.setIsOnChangedLine(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(component, issue)); + + if (issue.isNewCodeReferenceIssue() && !issue.isOnChangedLine()) { + issue.setIsNoLongerNewCodeReferenceIssue(true); + issue.setIsNewCodeReferenceIssue(false); + } + } + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifier.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifier.java index 9c5aa13e77e..5b3e8a6cce4 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifier.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifier.java @@ -41,7 +41,7 @@ public class NewIssueClassifier { public boolean isEnabled() { return analysisMetadataHolder.isPullRequest() || periodHolder.hasPeriodDate() || - (periodHolder.hasPeriod() && periodHolder.getPeriod().getMode().equals(NewCodePeriodType.REFERENCE_BRANCH.name())); + (periodHolder.hasPeriod() && isOnBranchUsingReferenceBranch()); } public boolean isNew(Component component, DefaultIssue issue) { @@ -54,17 +54,21 @@ public class NewIssueClassifier { return periodHolder.getPeriod().isOnPeriod(issue.creationDate()); } - if (periodHolder.getPeriod().getMode().equals(NewCodePeriodType.REFERENCE_BRANCH.name())) { - issue.setIsOnReferencedBranch(true); - boolean isOnChangedLine = hasAtLeastOneLocationOnChangedLines(component, issue); - issue.setIsOnChangedLine(isOnChangedLine); - return isOnChangedLine; + if (isOnBranchUsingReferenceBranch()) { + return hasAtLeastOneLocationOnChangedLines(component, issue); } } return false; } - private boolean hasAtLeastOneLocationOnChangedLines(Component component, DefaultIssue issue) { + public boolean isOnBranchUsingReferenceBranch() { + if (periodHolder.hasPeriod()) { + return periodHolder.getPeriod().getMode().equals(NewCodePeriodType.REFERENCE_BRANCH.name()); + } + return false; + } + + public boolean hasAtLeastOneLocationOnChangedLines(Component component, DefaultIssue issue) { if (component.getType() != Component.Type.FILE) { return false; } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java index c744da8bea6..5b8ecfca95a 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java @@ -75,6 +75,7 @@ public class PersistIssuesStep implements ComputationStep { CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse()) { List<DefaultIssue> addedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE); List<DefaultIssue> updatedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE); + List<DefaultIssue> noLongerNewIssues = new ArrayList<>(ISSUE_BATCHING_SIZE); IssueMapper mapper = dbSession.getMapper(IssueMapper.class); IssueChangeMapper changeMapper = dbSession.getMapper(IssueChangeMapper.class); @@ -92,10 +93,17 @@ public class PersistIssuesStep implements ComputationStep { persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper); updatedIssues.clear(); } + } else if (issue.isNoLongerNewCodeReferenceIssue()) { + noLongerNewIssues.add(issue); + if (noLongerNewIssues.size() >= ISSUE_BATCHING_SIZE) { + persistNoLongerNewIssues(statistics, noLongerNewIssues, mapper); + noLongerNewIssues.clear(); + } } } persistNewIssues(statistics, addedIssues, mapper, changeMapper); persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper); + persistNoLongerNewIssues(statistics, noLongerNewIssues, mapper); flushSession(dbSession); } finally { statistics.dumpTo(context); @@ -113,7 +121,7 @@ public class PersistIssuesStep implements ComputationStep { IssueDto dto = IssueDto.toDtoForComputationInsert(i, ruleUuid, now); mapper.insert(dto); if (i.isOnReferencedBranch() && i.isOnChangedLine()) { - mapper.insertAsNewOnReferenceBranch(NewCodeReferenceIssueDto.fromIssueDto(dto, now, uuidFactory)); + mapper.insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto.fromIssueDto(dto, now, uuidFactory)); } statistics.inserts++; }); @@ -149,6 +157,18 @@ public class PersistIssuesStep implements ComputationStep { updatedIssues.forEach(i -> issueStorage.insertChanges(changeMapper, i, uuidFactory)); } + private static void persistNoLongerNewIssues(IssueStatistics statistics, List<DefaultIssue> noLongerNewIssues, IssueMapper mapper) { + if (noLongerNewIssues.isEmpty()) { + return; + } + + noLongerNewIssues.forEach(i -> { + mapper.deleteAsNewCodeOnReferenceBranch(i.key()); + statistics.updates++; + }); + + } + private static void flushSession(DbSession dbSession) { dbSession.flushStatements(); dbSession.commit(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java index 21c80615e43..40bf25a4101 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java @@ -132,6 +132,7 @@ public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> { defaultIssue.setSendNotifications(next.getSendNotifications()); defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null); defaultIssue.setQuickFixAvailable(next.getQuickFixAvailable()); + defaultIssue.setIsNoLongerNewCodeReferenceIssue(next.getIsNoLongerNewCodeReferenceIssue()); for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) { defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs)); @@ -182,6 +183,7 @@ public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> { builder.setSendNotifications(defaultIssue.mustSendNotifications()); ofNullable(defaultIssue.selectedAt()).ifPresent(builder::setSelectedAt); builder.setQuickFixAvailable(defaultIssue.isQuickFixAvailable()); + builder.setIsNoLongerNewCodeReferenceIssue(defaultIssue.isNoLongerNewCodeReferenceIssue()); for (FieldDiffs fieldDiffs : defaultIssue.changes()) { builder.addChanges(toProtoIssueChanges(fieldDiffs)); diff --git a/server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto b/server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto index a873fefd52b..29bce80c281 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto +++ b/server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto @@ -56,7 +56,7 @@ message Issue { optional bool isFromExternalRuleEngine = 25; - // FUNCTIONAL DATES + // FUNCTIONAL DATES optional int64 creationDate = 26; optional int64 updateDate = 27; optional int64 closeDate = 28; @@ -78,6 +78,7 @@ message Issue { optional bool isOnReferencedBranch = 40; optional bool isOnChangedLine = 41; + optional bool isNoLongerNewCodeReferenceIssue = 42; } message Comment { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java index be880239e00..e89a587a834 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java @@ -19,7 +19,6 @@ */ package org.sonar.ce.task.projectanalysis.container; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -28,7 +27,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.Nonnull; import org.junit.Test; import org.picocontainer.DefaultPicoContainer; import org.picocontainer.PicoContainer; diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java index 913dc740343..d96928ad8b7 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java @@ -115,7 +115,8 @@ public class IssueLifecycleTest { DefaultIssue raw = new DefaultIssue() .setKey("raw"); DefaultIssue fromShort = new DefaultIssue() - .setKey("short"); + .setKey("short") + .setIsNewCodeReferenceIssue(true); fromShort.setResolution("resolution"); fromShort.setStatus("status"); @@ -161,6 +162,7 @@ public class IssueLifecycleTest { assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_BRANCH); assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("#2"); assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("master"); + assertThat(raw.isNewCodeReferenceIssue()).isTrue(); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssuesOnReferenceBranchVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssuesOnReferenceBranchVisitorTest.java new file mode 100644 index 00000000000..37a4aa58394 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssuesOnReferenceBranchVisitorTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.ce.task.projectanalysis.issue; + +import org.junit.Test; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.core.issue.DefaultIssue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class IssuesOnReferenceBranchVisitorTest { + + private final NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class); + private final Component component = mock(Component.class); + private final DefaultIssue issue = mock(DefaultIssue.class); + + private final IssueOnReferenceBranchVisitor underTest = new IssueOnReferenceBranchVisitor(newIssueClassifier); + + @Test + public void issue_is_not_changed_when_newIssueClassifier_is_not_enabled() { + when(newIssueClassifier.isEnabled()).thenReturn(false); + + underTest.onIssue(component, issue); + verifyNoInteractions(issue); + } + + @Test + public void handles_issue_not_on_branch_using_reference_branch() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + when(newIssueClassifier.isOnBranchUsingReferenceBranch()).thenReturn(false); + + underTest.onIssue(component, issue); + verify(issue).setIsOnReferencedBranch(false); + verify(issue).isOnReferencedBranch(); + verifyNoMoreInteractions(issue); + } + + @Test + public void handles_overall_code_issue_on_branch_using_reference_branch() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + when(newIssueClassifier.isOnBranchUsingReferenceBranch()).thenReturn(true); + when(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(component, issue)).thenReturn(true); + when(issue.isOnReferencedBranch()).thenReturn(true); + when(issue.isNewCodeReferenceIssue()).thenReturn(false); + + underTest.onIssue(component, issue); + verify(issue).setIsOnReferencedBranch(true); + verify(issue).isOnReferencedBranch(); + verify(issue).setIsOnChangedLine(true); + verify(issue).isNewCodeReferenceIssue(); + verifyNoMoreInteractions(issue); + } + + @Test + public void handles_new_code_issue_on_branch_using_reference_branch_which_is_still_new() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + when(newIssueClassifier.isOnBranchUsingReferenceBranch()).thenReturn(true); + when(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(component, issue)).thenReturn(true); + when(issue.isOnReferencedBranch()).thenReturn(true); + when(issue.isNewCodeReferenceIssue()).thenReturn(true); + when(issue.isOnChangedLine()).thenReturn(true); + + underTest.onIssue(component, issue); + verify(issue).setIsOnReferencedBranch(true); + verify(issue).isOnReferencedBranch(); + verify(issue).setIsOnChangedLine(true); + verify(issue).isNewCodeReferenceIssue(); + verify(issue).isOnChangedLine(); + verifyNoMoreInteractions(issue); + } + + @Test + public void handles_new_code_issue_on_branch_using_reference_branch_which_is_no_longer_new() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + when(newIssueClassifier.isOnBranchUsingReferenceBranch()).thenReturn(true); + when(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(component, issue)).thenReturn(false); + when(issue.isOnReferencedBranch()).thenReturn(true); + when(issue.isNewCodeReferenceIssue()).thenReturn(true); + when(issue.isOnChangedLine()).thenReturn(false); + + underTest.onIssue(component, issue); + verify(issue).setIsOnReferencedBranch(true); + verify(issue).isOnReferencedBranch(); + verify(issue).setIsOnChangedLine(false); + verify(issue).isNewCodeReferenceIssue(); + verify(issue).setIsNoLongerNewCodeReferenceIssue(true); + verify(issue).setIsNewCodeReferenceIssue(false); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifierTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifierTest.java index ed03ba5105d..a7afee4923c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifierTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewIssueClassifierTest.java @@ -109,8 +109,8 @@ public class NewIssueClassifierTest { .build()) .build()); assertThat(newIssueClassifier.isNew(file, issue)).isTrue(); - verify(issue).setIsOnReferencedBranch(true); - verify(issue).setIsOnChangedLine(true); + assertThat(newIssueClassifier.isOnBranchUsingReferenceBranch()).isTrue(); + assertThat(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(file, issue)).isTrue(); } @Test @@ -130,8 +130,52 @@ public class NewIssueClassifierTest { .build()) .build()); assertThat(newIssueClassifier.isNew(file, issue)).isFalse(); - verify(issue).setIsOnReferencedBranch(true); - verify(issue).setIsOnChangedLine(false); + assertThat(newIssueClassifier.isOnBranchUsingReferenceBranch()).isTrue(); + assertThat(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(file, issue)).isFalse(); + } + + @Test + public void isNew_returns_false_for_issue_which_was_new_but_it_is_not_located_on_changed_lines_anymore() { + periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null)); + Component file = mock(Component.class); + DefaultIssue issue = mock(DefaultIssue.class); + when(file.getType()).thenReturn(Component.Type.FILE); + when(file.getUuid()).thenReturn("fileUuid"); + when(newLinesRepository.getNewLines(file)).thenReturn(Optional.of(Set.of(2, 3))); + when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder() + .setTextRange(DbCommons.TextRange.newBuilder() + .setStartLine(10) + .setStartOffset(1) + .setEndLine(10) + .setEndOffset(2) + .build()) + .build()); + when(issue.isNewCodeReferenceIssue()).thenReturn(true); + assertThat(newIssueClassifier.isNew(file, issue)).isFalse(); + assertThat(newIssueClassifier.isOnBranchUsingReferenceBranch()).isTrue(); + assertThat(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(file, issue)).isFalse(); + } + + @Test + public void isNew_returns_true_for_issue_which_was_new_and_is_still_located_on_changed_lines() { + periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null)); + Component file = mock(Component.class); + DefaultIssue issue = mock(DefaultIssue.class); + when(file.getType()).thenReturn(Component.Type.FILE); + when(file.getUuid()).thenReturn("fileUuid"); + when(newLinesRepository.getNewLines(file)).thenReturn(Optional.of(Set.of(2, 3))); + when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder() + .setTextRange(DbCommons.TextRange.newBuilder() + .setStartLine(2) + .setStartOffset(1) + .setEndLine(2) + .setEndOffset(2) + .build()) + .build()); + when(issue.isNewCodeReferenceIssue()).thenReturn(true); + assertThat(newIssueClassifier.isNew(file, issue)).isTrue(); + assertThat(newIssueClassifier.isOnBranchUsingReferenceBranch()).isTrue(); + assertThat(newIssueClassifier.hasAtLeastOneLocationOnChangedLines(file, issue)).isTrue(); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java index aeeea14ee82..e53c0fe70d0 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java @@ -68,6 +68,7 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue; public class PersistIssuesStepTest extends BaseStepTest { @@ -162,9 +163,7 @@ public class PersistIssuesStepTest extends BaseStepTest { assertThat(result.getStatus()).isEqualTo(STATUS_OPEN); assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant()); assertThat(result.getTags()).containsExactlyInAnyOrder("test"); - - boolean isNewCodeOnReferencedBranch = dbClient.issueDao().isNewCodeOnReferencedBranch(session, result.getKey()); - assertThat(isNewCodeOnReferencedBranch).isFalse(); + assertThat(result.isNewCodeReferenceIssue()).isFalse(); List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey)); assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE); @@ -211,9 +210,7 @@ public class PersistIssuesStepTest extends BaseStepTest { assertThat(result.getStatus()).isEqualTo(STATUS_OPEN); assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant()); assertThat(result.getTags()).isEmpty(); - - boolean isNewCodeOnReferencedBranch = dbClient.issueDao().isNewCodeOnReferencedBranch(session, result.getKey()); - assertThat(isNewCodeOnReferencedBranch).isFalse(); + assertThat(result.isNewCodeReferenceIssue()).isFalse(); assertThat(dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey))).isEmpty(); assertThat(context.getStatistics().getAll()).contains( @@ -272,9 +269,7 @@ public class PersistIssuesStepTest extends BaseStepTest { assertThat(result.getSeverity()).isEqualTo(BLOCKER); assertThat(result.getStatus()).isEqualTo(STATUS_OPEN); assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant()); - - boolean isNewCodeOnReferencedBranch = dbClient.issueDao().isNewCodeOnReferencedBranch(session, result.getKey()); - assertThat(isNewCodeOnReferencedBranch).isTrue(); + assertThat(result.isNewCodeReferenceIssue()).isTrue(); List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey)); assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE); @@ -354,9 +349,7 @@ public class PersistIssuesStepTest extends BaseStepTest { assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant()); assertThat(context.getStatistics().getAll()).contains( entry("inserts", "1"), entry("updates", "0"), entry("merged", "0")); - - boolean isNewCodeOnReferencedBranch = dbClient.issueDao().isNewCodeOnReferencedBranch(session, result.getKey()); - assertThat(isNewCodeOnReferencedBranch).isTrue(); + assertThat(result.isNewCodeReferenceIssue()).isTrue(); } @Test @@ -391,6 +384,60 @@ public class PersistIssuesStepTest extends BaseStepTest { } @Test + public void handle_no_longer_new_issue() { + RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01")); + db.rules().insert(rule); + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project, null)); + when(system2.now()).thenReturn(NOW); + String issueKey = "ISSUE-5"; + + DefaultIssue defaultIssue = new DefaultIssue() + .setKey(issueKey) + .setType(RuleType.CODE_SMELL) + .setRuleKey(rule.getKey()) + .setComponentUuid(file.uuid()) + .setComponentKey(file.getKey()) + .setProjectUuid(project.uuid()) + .setProjectKey(project.getKey()) + .setSeverity(BLOCKER) + .setStatus(STATUS_OPEN) + .setNew(true) + .setIsOnReferencedBranch(true) + .setIsOnChangedLine(true) + .setIsNewCodeReferenceIssue(true) + .setIsNoLongerNewCodeReferenceIssue(false) + .setCopied(false) + .setType(RuleType.BUG) + .setCreationDate(new Date(NOW)) + .setSelectedAt(NOW); + + IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW); + dbClient.issueDao().insert(session, issueDto); + dbClient.issueDao().insertAsNewCodeOnReferenceBranch(session, newCodeReferenceIssue(issueDto)); + session.commit(); + + IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey); + assertThat(result.isNewCodeReferenceIssue()).isTrue(); + + protoIssueCache.newAppender().append(defaultIssue.setNew(false) + .setIsOnReferencedBranch(true) + .setIsOnChangedLine(false) + .setIsNewCodeReferenceIssue(false) + .setIsNoLongerNewCodeReferenceIssue(true)) + .close(); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(context.getStatistics().getAll()).contains( + entry("inserts", "0"), entry("updates", "1"), entry("merged", "0")); + + result = dbClient.issueDao().selectOrFailByKey(session, issueKey); + assertThat(result.isNewCodeReferenceIssue()).isFalse(); + } + + @Test public void add_comment() { ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); 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 0e0de4ed55a..bff947b76fe 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 @@ -58,10 +58,6 @@ public class IssueDao implements Dao { return executeLargeInputs(keys, mapper(session)::selectByKeys); } - public boolean isNewCodeOnReferencedBranch(DbSession session, String issueKey) { - return mapper(session).isNewCodeOnReferencedBranch(issueKey); - } - public Set<String> selectIssueKeysByComponentUuid(DbSession session, String componentUuid) { return mapper(session).selectIssueKeysByComponentUuid(componentUuid); } @@ -111,9 +107,12 @@ public class IssueDao implements Dao { mapper(session).update(dto); } - public void insertAsNewOnReferenceBranch(DbSession session, NewCodeReferenceIssueDto dto) { - IssueMapper mapper = mapper(session); - mapper.insertAsNewOnReferenceBranch(dto); + public void insertAsNewCodeOnReferenceBranch(DbSession session, NewCodeReferenceIssueDto dto) { + mapper(session).insertAsNewCodeOnReferenceBranch(dto); + } + + public void deleteAsNewCodeOnReferenceBranch(DbSession session, String issueKey) { + mapper(session).deleteAsNewCodeOnReferenceBranch(issueKey); } private static IssueMapper mapper(DbSession session) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java index f1bc6419665..323a1c80d65 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java @@ -77,6 +77,7 @@ public final class IssueDto implements Serializable { private long createdAt; private long updatedAt; private boolean quickFixAvailable; + private boolean isNewCodeReferenceIssue; // functional dates stored as Long private Long issueCreationDate; @@ -141,6 +142,8 @@ public final class IssueDto implements Serializable { .setIssueUpdateDate(issue.updateDate()) .setSelectedAt(issue.selectedAt()) .setQuickFixAvailable(issue.isQuickFixAvailable()) + .setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue()) + // technical dates .setCreatedAt(now) @@ -188,6 +191,7 @@ public final class IssueDto implements Serializable { .setIssueUpdateDate(issue.updateDate()) .setSelectedAt(issue.selectedAt()) .setQuickFixAvailable(issue.isQuickFixAvailable()) + .setIsNewCodeReferenceIssue(issue.isNewCodeReferenceIssue()) // technical date .setUpdatedAt(now); @@ -702,6 +706,15 @@ public final class IssueDto implements Serializable { return this; } + public boolean isNewCodeReferenceIssue() { + return isNewCodeReferenceIssue; + } + + public IssueDto setIsNewCodeReferenceIssue(boolean isNewCodeReferenceIssue) { + this.isNewCodeReferenceIssue = isNewCodeReferenceIssue; + return this; + } + public int getType() { return type; } @@ -758,6 +771,7 @@ public final class IssueDto implements Serializable { issue.setLocations(parseLocations()); issue.setIsFromExternalRuleEngine(isExternal); issue.setQuickFixAvailable(quickFixAvailable); + issue.setIsNewCodeReferenceIssue(isNewCodeReferenceIssue); return issue; } } 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 49399d49cf9..bf783317577 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 @@ -37,8 +37,6 @@ public interface IssueMapper { List<IssueDto> selectByKeys(List<String> keys); - boolean isNewCodeOnReferencedBranch(@Param("issueKey") String issueKey); - Set<String> selectIssueKeysByComponentUuid(@Param("componentUuid") String componentUuid); List<IssueDto> selectByComponentUuidPaginated(@Param("componentUuid") String componentUuid, @@ -52,7 +50,9 @@ public interface IssueMapper { int update(IssueDto issue); - void insertAsNewOnReferenceBranch(NewCodeReferenceIssueDto issue); + void insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto issue); + + void deleteAsNewCodeOnReferenceBranch(String issueKey); int updateIfBeforeSelectedDate(IssueDto issue); 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 8e84d87f0f7..6f96b1ff854 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 @@ -39,7 +39,8 @@ root.kee as projectKey, i.project_uuid as projectUuid, i.issue_type as type, - i.quick_fix_available as quickFixAvailable + i.quick_fix_available as quickFixAvailable, + <include refid="isNewCodeReferenceIssue"/> </sql> <sql id="issueColumnsInInnerQuery"> @@ -70,6 +71,16 @@ i.quick_fix_available </sql> + <sql id="isNewCodeReferenceIssue" databaseId="mssql"> + cast(case when n.uuid is null then 0 else 1 end as bit) as isNewCodeReferenceIssue + </sql> + <sql id="isNewCodeReferenceIssue" databaseId="oracle"> + case when n.uuid is null then 0 else 1 end as isNewCodeReferenceIssue + </sql> + <sql id="isNewCodeReferenceIssue"> + n.uuid is not null as isNewCodeReferenceIssue + </sql> + <sql id="sortColumn"> <if test="query.sort() != null">, <choose> @@ -118,7 +129,7 @@ #{quickFixAvailable, jdbcType=BOOLEAN}) </insert> - <insert id="insertAsNewOnReferenceBranch" parameterType="NewCodeReferenceIssue" useGeneratedKeys="false"> + <insert id="insertAsNewCodeOnReferenceBranch" parameterType="NewCodeReferenceIssue" useGeneratedKeys="false"> INSERT INTO new_code_reference_issues (uuid, issue_key, created_at) VALUES ( #{uuid,jdbcType=VARCHAR}, @@ -126,6 +137,11 @@ #{createdAt,jdbcType=BIGINT}) </insert> + <insert id="deleteAsNewCodeOnReferenceBranch" parameterType="String" useGeneratedKeys="false"> + DELETE FROM new_code_reference_issues + where issue_key = #{issueKey, jdbcType=VARCHAR} + </insert> + <!-- IMPORTANT - invariant columns can't be updated. See IssueDto#toDtoForUpdate() --> @@ -190,30 +206,10 @@ inner join rules r on r.uuid=i.rule_uuid inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where i.kee=#{kee,jdbcType=VARCHAR} </select> - <sql id="isNewCodeOnReferencedBranchSql"> - select - case when exists - ( - select i.uuid from new_code_reference_issues i - where i.issue_key = #{issueKey,jdbcType=VARCHAR} - ) - then 1 - else 0 - end - </sql> - - <select id="isNewCodeOnReferencedBranch" parameterType="String" resultType="boolean"> - <include refid="isNewCodeOnReferencedBranchSql"/> - </select> - - <select id="isNewCodeOnReferencedBranch" parameterType="String" resultType="boolean" databaseId="oracle"> - <include refid="isNewCodeOnReferencedBranchSql"/> - from dual - </select> - <select id="scrollNonClosedByComponentUuid" parameterType="String" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY"> select @@ -222,6 +218,7 @@ inner join rules r on r.uuid=i.rule_uuid inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and i.status <> 'CLOSED' @@ -234,6 +231,7 @@ inner join rules r on r.uuid=i.rule_uuid inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where (r.is_external is NULL or r.is_external = ${_false}) and i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and @@ -256,6 +254,8 @@ ic.issue_key = i.kee and ic.change_type = 'diff' and ic.change_data like '%status=%|CLOSED%' + left join new_code_reference_issues n on + i.kee = n.issue_key where i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and i.status = 'CLOSED' @@ -291,6 +291,7 @@ inner join rules r on r.uuid=i.rule_uuid inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where i.kee in <foreach collection="list" open="(" close=")" item="key" separator=","> #{key,jdbcType=VARCHAR} @@ -304,6 +305,7 @@ inner join rules r on r.uuid=i.rule_uuid inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where i.kee in <foreach collection="keys" open="(" close=")" item="key" separator=","> @@ -341,6 +343,7 @@ inner join rules r on r.uuid = i.rule_uuid inner join components p on p.uuid = i.component_uuid inner join components root on root.uuid = i.project_uuid + left join new_code_reference_issues n on i.kee = n.issue_key where (r.is_external is NULL or r.is_external = ${_false}) and i.project_uuid = #{projectUuid, jdbcType=VARCHAR} and @@ -402,6 +405,7 @@ inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid left join users u on i.assignee = u.uuid + left join new_code_reference_issues n on i.kee = n.issue_key where i.project_uuid=#{componentUuid,jdbcType=VARCHAR} order by i.issue_creation_date ASC limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER} @@ -424,6 +428,7 @@ inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid left join users u on i.assignee = u.uuid + left join new_code_reference_issues n on i.kee = n.issue_key </select> <select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue" databaseId="oracle"> @@ -447,6 +452,7 @@ inner join components p on p.uuid=i.component_uuid inner join components root on root.uuid=i.project_uuid left join users u on i.assignee = u.uuid + left join new_code_reference_issues n on i.kee = n.issue_key </select> </mapper> 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 afa5e7b28de..1621667f538 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 @@ -444,7 +444,7 @@ public class IssueDaoTest { } @Test - public void selectByKey_givenOneIssueWithQuickFix_selectOneIssueWithQuickFix2() { + public void selectByKey_givenOneIssueNewOnReferenceBranch_selectOneIssueWithNewOnReferenceBranch() { prepareIssuesComponent(); underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1) .setMessage("the message") @@ -459,12 +459,18 @@ public class IssueDaoTest { .setProjectUuid(PROJECT_UUID) .setQuickFixAvailable(true)); IssueDto issue1 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1); + IssueDto issue2 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY2); - underTest.insertAsNewOnReferenceBranch(db.getSession(), - newCodeReferenceIssue(issue1)); + assertThat(issue1.isNewCodeReferenceIssue()).isFalse(); + assertThat(issue2.isNewCodeReferenceIssue()).isFalse(); - assertThat(underTest.isNewCodeOnReferencedBranch(db.getSession(), ISSUE_KEY1)).isTrue(); - assertThat(underTest.isNewCodeOnReferencedBranch(db.getSession(), ISSUE_KEY2)).isFalse(); + underTest.insertAsNewCodeOnReferenceBranch(db.getSession(), newCodeReferenceIssue(issue1)); + + assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).isNewCodeReferenceIssue()).isTrue(); + assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY2).isNewCodeReferenceIssue()).isFalse(); + + underTest.deleteAsNewCodeOnReferenceBranch(db.getSession(), ISSUE_KEY1); + assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).isNewCodeReferenceIssue()).isFalse(); } private static IssueDto newIssueDto(String key) { diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java index 8f1b5dabeb7..b8c221c6360 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java @@ -112,6 +112,7 @@ public class IssueDtoTest { assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(updatedAt, Calendar.SECOND)); assertThat(issue.closeDate()).isEqualTo(DateUtils.truncate(closedAt, Calendar.SECOND)); assertThat(issue.isNew()).isFalse(); + assertThat(issue.isNewCodeReferenceIssue()).isFalse(); } @Test @@ -182,7 +183,7 @@ public class IssueDtoTest { "path/to/module/uuid", "123", "projectKey", "key=value", "ruleUuid"); assertThat(issueDto.isQuickFixAvailable()).isTrue(); - + assertThat(issueDto.isNewCodeReferenceIssue()).isTrue(); } @Test @@ -214,7 +215,7 @@ public class IssueDtoTest { "path/to/module/uuid", "123", "projectKey", "key=value"); assertThat(issueDto.isQuickFixAvailable()).isTrue(); - + assertThat(issueDto.isNewCodeReferenceIssue()).isTrue(); } private DefaultIssue createExampleDefaultIssue(Date dateNow) { @@ -246,7 +247,8 @@ public class IssueDtoTest { .setCloseDate(dateNow) .setUpdateDate(dateNow) .setSelectedAt(dateNow.getTime()) - .setQuickFixAvailable(true); + .setQuickFixAvailable(true) + .setIsNewCodeReferenceIssue(true); return defaultIssue; } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java index 19b84c9db2c..a159c50505f 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java @@ -1126,11 +1126,17 @@ public class PurgeDaoTest { // others remain assertThat(db.countRowsOfTable("issues")).isEqualTo(2); - assertThat(db.getDbClient().issueDao().selectByKey(dbSession, notOldEnoughClosed.getKey())).isNotEmpty(); - assertThat(db.getDbClient().issueDao().isNewCodeOnReferencedBranch(dbSession, notOldEnoughClosed.getKey())).isTrue(); - assertThat(db.getDbClient().issueDao().selectByKey(dbSession, notClosed.getKey())).isNotEmpty(); - assertThat(db.getDbClient().issueDao().isNewCodeOnReferencedBranch(dbSession, notClosed.getKey())).isTrue(); - assertThat(db.getDbClient().issueDao().isNewCodeOnReferencedBranch(dbSession, oldClosed.getKey())).isFalse(); + + Optional<IssueDto> notOldEnoughClosedFromQuery = db.getDbClient().issueDao().selectByKey(dbSession, notOldEnoughClosed.getKey()); + assertThat(notOldEnoughClosedFromQuery).isNotEmpty(); + assertThat(notOldEnoughClosedFromQuery.get().isNewCodeReferenceIssue()).isTrue(); + + Optional<IssueDto> notClosedFromQuery = db.getDbClient().issueDao().selectByKey(dbSession, notClosed.getKey()); + assertThat(notClosedFromQuery).isNotEmpty(); + assertThat(notClosedFromQuery.get().isNewCodeReferenceIssue()).isTrue(); + + Optional<IssueDto> oldClosedFromQuery = db.getDbClient().issueDao().selectByKey(dbSession, oldClosed.getKey()); + assertThat(oldClosedFromQuery).isEmpty(); } @Test diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java index 10ce0985873..632368599a0 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java @@ -222,7 +222,7 @@ public class IssueDbTester { * Inserts an issue as new code in a branch using reference branch for new code */ public void insertNewCodeReferenceIssue(NewCodeReferenceIssueDto dto) { - db.getDbClient().issueDao().insertAsNewOnReferenceBranch(db.getSession(), dto); + db.getDbClient().issueDao().insertAsNewCodeOnReferenceBranch(db.getSession(), dto); db.commit(); } 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 89aa9a7fa3b..7d8a8874896 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 @@ -125,6 +125,10 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. private Long selectedAt; private boolean quickFixAvailable; + private boolean isNewCodeReferenceIssue; + + // true if the issue is no longer new in its branch + private boolean isNoLongerNewCodeReferenceIssue = false; @Override public String key() { @@ -548,6 +552,24 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. return this; } + public boolean isNewCodeReferenceIssue() { + return isNewCodeReferenceIssue; + } + + public DefaultIssue setIsNewCodeReferenceIssue(boolean isNewCodeReferenceIssue) { + this.isNewCodeReferenceIssue = isNewCodeReferenceIssue; + return this; + } + + public boolean isNoLongerNewCodeReferenceIssue() { + return isNoLongerNewCodeReferenceIssue; + } + + public DefaultIssue setIsNoLongerNewCodeReferenceIssue(boolean isNoLongerNewCodeReferenceIssue) { + this.isNoLongerNewCodeReferenceIssue = isNoLongerNewCodeReferenceIssue; + return this; + } + @CheckForNull public FieldDiffs currentChange() { return currentChange; diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java index 874272d2d40..423dc536b00 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java @@ -58,6 +58,8 @@ public class DefaultIssueTest { .setNew(true) .setIsOnReferencedBranch(true) .setIsOnChangedLine(true) + .setIsNewCodeReferenceIssue(true) + .setIsNoLongerNewCodeReferenceIssue(true) .setBeingClosed(true) .setOnDisabledRule(true) .setCopied(true) @@ -87,6 +89,8 @@ public class DefaultIssueTest { assertThat(issue.isNew()).isTrue(); assertThat(issue.isOnReferencedBranch()).isTrue(); assertThat(issue.isOnChangedLine()).isTrue(); + assertThat(issue.isNewCodeReferenceIssue()).isTrue(); + assertThat(issue.isNoLongerNewCodeReferenceIssue()).isTrue(); assertThat(issue.isCopied()).isTrue(); assertThat(issue.isBeingClosed()).isTrue(); assertThat(issue.isOnDisabledRule()).isTrue(); |