From 6365d7447e159d3cc255e381b67894f3827bcd6c Mon Sep 17 00:00:00 2001 From: Steve Marion Date: Wed, 26 Jul 2023 17:49:52 +0200 Subject: [PATCH] SONAR-20061 purge used anticipated_transition --- .../step/PersistIssuesStepIT.java | 49 ++++++++++ .../AnticipatedTransitionRepository.java | 1 - .../AnticipatedTransitionRepositoryImpl.java | 6 +- ...itionIssuesToAnticipatedStatesVisitor.java | 2 +- .../step/PersistIssuesStep.java | 27 +++--- .../util/cache/ProtobufIssueDiskCache.java | 4 + .../src/main/protobuf/issue_cache.proto | 1 + ...nIssuesToAnticipatedStatesVisitorTest.java | 10 +- .../db/issue/AnticipatedTransitionMapper.xml | 1 - .../java/org/sonar/db/DbTester.java | 7 ++ .../AnticipatedTransitionDbTester.java | 64 +++++++++++++ .../org/sonar/db/issue/IssueDbTester.java | 1 - .../org/sonar/server/issue/IssueStorage.java | 2 +- .../sonar/server/issue/IssueStorageTest.java | 91 +++++++++++++++++++ .../AnticipatedTransitionParser.java | 1 + .../AnticipatedTransitionHandlerTest.java | 3 +- .../AnticipatedTransitionParserTest.java | 2 + .../core/issue/AnticipatedTransition.java | 7 ++ .../org/sonar/core/issue/DefaultIssue.java | 10 +- .../core/issue/AnticipatedTransitionTest.java | 2 + .../sonar/core/issue/DefaultIssueTest.java | 6 +- .../AnticipatedTransitionTrackerTest.java | 4 +- 22 files changed, 265 insertions(+), 36 deletions(-) create mode 100644 server/sonar-db-dao/src/testFixtures/java/org/sonar/db/anticipatedtransition/AnticipatedTransitionDbTester.java create mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueStorageTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java index 4c97694f98b..77e630aaa39 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java @@ -49,6 +49,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.AnticipatedTransitionDto; import org.sonar.db.issue.IssueChangeDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueMapper; @@ -628,4 +629,52 @@ public class PersistIssuesStepIT extends BaseStepTest { entry("inserts", "0"), entry("updates", "1"), entry("merged", "0")); } + @Test + public void when_anticipatedTransitionIsPresent_ItShouldBeDeleted() { + periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null)); + RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01")); + db.rules().insert(rule); + ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + session.commit(); + String issueKey = "ISSUE-4"; + + DefaultIssue newIssue = 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) + .setCreationDate(new Date(NOW)) + .setNew(true) + .setIsOnChangedLine(true) + .setType(RuleType.BUG); + + AnticipatedTransitionDto atDto = db.anticipatedTransitions().createForIssue(newIssue, "test_uuid", file.name()); + newIssue.setAnticipatedTransitionUuid(atDto.getUuid()); + + var defaultIssueCacheAppender = protoIssueCache.newAppender(); + defaultIssueCacheAppender.append(newIssue).close(); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey); + assertThat(result.getKey()).isEqualTo(issueKey); + assertThat(result.getRuleKey()).isEqualTo(rule.getKey()); + assertThat(result.getComponentUuid()).isEqualTo(file.uuid()); + assertThat(result.getProjectUuid()).isEqualTo(project.uuid()); + assertThat(result.getSeverity()).isEqualTo(BLOCKER); + assertThat(result.getStatus()).isEqualTo(STATUS_OPEN); + assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant()); + assertThat(context.getStatistics().getAll()).contains( + entry("inserts", "1"), entry("updates", "0"), entry("merged", "0")); + assertThat(result.isNewCodeReferenceIssue()).isTrue(); + + assertThat(db.anticipatedTransitions().selectByProjectUuid(project.uuid())).isEmpty(); + } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepository.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepository.java index e3d904f795e..06ac651040f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepository.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepository.java @@ -25,5 +25,4 @@ import org.sonar.core.issue.AnticipatedTransition; public interface AnticipatedTransitionRepository { Collection getAnticipatedTransitionByComponent(Component component); - } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepositoryImpl.java index e2a115a98c2..23ab266bb7a 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepositoryImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepositoryImpl.java @@ -56,12 +56,13 @@ public class AnticipatedTransitionRepositoryImpl implements AnticipatedTransitio private Collection getAnticipatedTransitions(List anticipatedTransitionDtos) { return anticipatedTransitionDtos .stream() - .map(this::getAnticipatedTransition) + .map(AnticipatedTransitionRepositoryImpl::getAnticipatedTransition) .toList(); } - private AnticipatedTransition getAnticipatedTransition(AnticipatedTransitionDto transitionDto) { + private static AnticipatedTransition getAnticipatedTransition(AnticipatedTransitionDto transitionDto) { return new AnticipatedTransition( + transitionDto.getUuid(), transitionDto.getProjectUuid(), transitionDto.getUserUuid(), RuleKey.parse(transitionDto.getRuleKey()), @@ -73,5 +74,4 @@ public class AnticipatedTransitionRepositoryImpl implements AnticipatedTransitio transitionDto.getComment() ); } - } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitor.java index 976ab894c2e..8cfb9e4e46f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitor.java @@ -67,7 +67,7 @@ public class TransitionIssuesToAnticipatedStatesVisitor extends IssueVisitor { private void performAnticipatedTransition(DefaultIssue issue, AnticipatedTransition anticipatedTransition) { issue.setBeingClosed(true); - issue.setAnticipatedTransitions(true); + issue.setAnticipatedTransitionUuid(anticipatedTransition.getUuid()); issueLifecycle.doManualTransition(issue, anticipatedTransition.getTransition(), anticipatedTransition.getUserUuid()); String transitionComment = anticipatedTransition.getComment(); String comment = Strings.isNotBlank(transitionComment) ? transitionComment : "Automatically transitioned from SonarLint"; 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 371691edae8..363b88507d4 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 @@ -36,6 +36,7 @@ import org.sonar.core.util.UuidFactory; import org.sonar.db.BatchSession; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.issue.AnticipatedTransitionMapper; import org.sonar.db.issue.IssueChangeMapper; import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueMapper; @@ -85,12 +86,13 @@ public class PersistIssuesStep implements ComputationStep { IssueMapper mapper = dbSession.getMapper(IssueMapper.class); IssueChangeMapper changeMapper = dbSession.getMapper(IssueChangeMapper.class); + AnticipatedTransitionMapper anticipatedTransitionMapper = dbSession.getMapper(AnticipatedTransitionMapper.class); while (issues.hasNext()) { DefaultIssue issue = issues.next(); if (issue.isNew() || issue.isCopied()) { addedIssues.add(issue); if (addedIssues.size() >= ISSUE_BATCHING_SIZE) { - persistNewIssues(statistics, addedIssues, mapper, changeMapper); + persistNewIssues(statistics, addedIssues, mapper, changeMapper, anticipatedTransitionMapper); addedIssues.clear(); } } else if (issue.isChanged()) { @@ -113,7 +115,7 @@ public class PersistIssuesStep implements ComputationStep { } } } - persistNewIssues(statistics, addedIssues, mapper, changeMapper); + persistNewIssues(statistics, addedIssues, mapper, changeMapper, anticipatedTransitionMapper); persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper); persistNoLongerNewIssues(statistics, noLongerNewIssues, mapper); persistNewCodeIssuesToMigrate(statistics, newCodeIssuesToMigrate, mapper); @@ -123,23 +125,22 @@ public class PersistIssuesStep implements ComputationStep { } } - private void persistNewIssues(IssueStatistics statistics, List addedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) { - if (addedIssues.isEmpty()) { - return; - } + private void persistNewIssues(IssueStatistics statistics, List addedIssues, + IssueMapper mapper, IssueChangeMapper changeMapper, AnticipatedTransitionMapper anticipatedTransitionMapper) { - long now = system2.now(); - addedIssues.forEach(i -> { - String ruleUuid = ruleRepository.getByKey(i.ruleKey()).getUuid(); - IssueDto dto = IssueDto.toDtoForComputationInsert(i, ruleUuid, now); + final long now = system2.now(); + + addedIssues.forEach(addedIssue -> { + String ruleUuid = ruleRepository.getByKey(addedIssue.ruleKey()).getUuid(); + IssueDto dto = IssueDto.toDtoForComputationInsert(addedIssue, ruleUuid, now); mapper.insert(dto); - if (isOnBranchUsingReferenceBranch() && i.isOnChangedLine()) { + if (isOnBranchUsingReferenceBranch() && addedIssue.isOnChangedLine()) { mapper.insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto.fromIssueDto(dto, now, uuidFactory)); } statistics.inserts++; + issueStorage.insertChanges(changeMapper, addedIssue, uuidFactory); + addedIssue.getAnticipatedTransitionUuid().ifPresent(anticipatedTransitionMapper::delete); }); - - addedIssues.forEach(i -> issueStorage.insertChanges(changeMapper, i, uuidFactory)); } private void persistUpdatedIssues(IssueStatistics statistics, List updatedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) { 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 f0f251f95c3..69653021a60 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 @@ -136,6 +136,9 @@ public class ProtobufIssueDiskCache implements DiskCache { defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null); defaultIssue.setQuickFixAvailable(next.getQuickFixAvailable()); defaultIssue.setIsNoLongerNewCodeReferenceIssue(next.getIsNoLongerNewCodeReferenceIssue()); + if (next.hasAnticipatedTransitionUuid()) { + defaultIssue.setAnticipatedTransitionUuid(next.getAnticipatedTransitionUuid()); + } for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) { defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs)); @@ -189,6 +192,7 @@ public class ProtobufIssueDiskCache implements DiskCache { ofNullable(defaultIssue.selectedAt()).ifPresent(builder::setSelectedAt); builder.setQuickFixAvailable(defaultIssue.isQuickFixAvailable()); builder.setIsNoLongerNewCodeReferenceIssue(defaultIssue.isNoLongerNewCodeReferenceIssue()); + defaultIssue.getAnticipatedTransitionUuid().ifPresent(builder::setAnticipatedTransitionUuid); 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 880424a347b..c75e6f58509 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 @@ -83,6 +83,7 @@ message Issue { optional sonarqube.db.issues.MessageFormattings messageFormattings = 45; optional string codeVariants = 46; optional string assigneeLogin = 47; + optional string anticipatedTransitionUuid = 48; } message Comment { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitorTest.java index 4cd4af882e9..0517c680c4a 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitorTest.java @@ -58,7 +58,7 @@ public class TransitionIssuesToAnticipatedStatesVisitorTest { underTest.onIssue(component, issue); assertThat(issue.isBeingClosed()).isTrue(); - assertThat(issue.hasAnticipatedTransitions()).isTrue(); + assertThat(issue.getAnticipatedTransitionUuid()).isPresent(); verify(issueLifecycle).doManualTransition(issue, "wontfix", "admin"); verify(issueLifecycle).addComment(issue, "doing the transition in an anticipated way", "admin"); } @@ -74,7 +74,7 @@ public class TransitionIssuesToAnticipatedStatesVisitorTest { underTest.onIssue(component, issue); assertThat(issue.isBeingClosed()).isFalse(); - assertThat(issue.hasAnticipatedTransitions()).isFalse(); + assertThat(issue.getAnticipatedTransitionUuid()).isNotPresent(); verifyNoInteractions(issueLifecycle); } @@ -89,7 +89,7 @@ public class TransitionIssuesToAnticipatedStatesVisitorTest { underTest.onIssue(component, issue); assertThat(issue.isBeingClosed()).isTrue(); - assertThat(issue.hasAnticipatedTransitions()).isTrue(); + assertThat(issue.getAnticipatedTransitionUuid()).isPresent(); verify(issueLifecycle).doManualTransition(issue, "wontfix", "admin"); verify(issueLifecycle).addComment(issue, "Automatically transitioned from SonarLint", "admin"); } @@ -115,11 +115,11 @@ public class TransitionIssuesToAnticipatedStatesVisitorTest { } private Collection getAnticipatedTransitions(String projecKey, String fileName) { - return Stream.of(new AnticipatedTransition(projecKey, "admin", RuleKey.parse("repo:id"), "issue message", fileName, 1, "abcdefghi", "wontfix", "doing the transition in an anticipated way")).collect(Collectors.toList()); + return Stream.of(new AnticipatedTransition("atuuid", projecKey, "admin", RuleKey.parse("repo:id"), "issue message", fileName, 1, "abcdefghi", "wontfix", "doing the transition in an anticipated way")).collect(Collectors.toList()); } private Collection getAnticipatedTransitionsWithEmptyComment(String projecKey, String fileName) { - return Stream.of(new AnticipatedTransition(projecKey, "admin", RuleKey.parse("repo:id"), "issue message", fileName, 1, "abcdefghi", "wontfix", null)).collect(Collectors.toList()); + return Stream.of(new AnticipatedTransition("atuuid", projecKey, "admin", RuleKey.parse("repo:id"), "issue message", fileName, 1, "abcdefghi", "wontfix", null)).collect(Collectors.toList()); } private Component getComponent(Component.Type type) { diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml index 3eda69945e6..0b8b34c1fa3 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml @@ -49,5 +49,4 @@ from anticipated_transitions at where at.project_uuid=#{projectUuid,jdbcType=VARCHAR} and at.file_path=#{filePath,jdbcType=VARCHAR} - diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java index b7f2ce1f3b6..64b76c1c932 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java @@ -32,6 +32,7 @@ import org.sonar.core.util.SequenceUuidFactory; import org.sonar.core.util.UuidFactory; import org.sonar.db.alm.integration.pat.AlmPatsDbTester; import org.sonar.db.almsettings.AlmSettingsDbTester; +import org.sonar.db.anticipatedtransition.AnticipatedTransitionDbTester; import org.sonar.db.audit.AuditDbTester; import org.sonar.db.audit.AuditPersister; import org.sonar.db.audit.NoOpAuditPersister; @@ -88,6 +89,7 @@ public class DbTester extends AbstractDbTester { private final AlmSettingsDbTester almSettingsDbTester; private final AlmPatsDbTester almPatsDbtester; private final AuditDbTester auditDbTester; + private final AnticipatedTransitionDbTester anticipatedTransitionDbTester; private DbTester(System2 system2, @Nullable String schemaPath, AuditPersister auditPersister, MyBatisConfExtension... confExtensions) { super(TestDbImpl.create(schemaPath, confExtensions)); @@ -117,6 +119,7 @@ public class DbTester extends AbstractDbTester { this.almSettingsDbTester = new AlmSettingsDbTester(this); this.almPatsDbtester = new AlmPatsDbTester(this); this.auditDbTester = new AuditDbTester(this); + this.anticipatedTransitionDbTester = new AnticipatedTransitionDbTester(this); } public static DbTester create() { @@ -247,6 +250,10 @@ public class DbTester extends AbstractDbTester { return auditDbTester; } + public AnticipatedTransitionDbTester anticipatedTransitions() { + return anticipatedTransitionDbTester; + } + @Override protected void after() { if (session != null) { diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/anticipatedtransition/AnticipatedTransitionDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/anticipatedtransition/AnticipatedTransitionDbTester.java new file mode 100644 index 00000000000..1eb8a7cc31b --- /dev/null +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/anticipatedtransition/AnticipatedTransitionDbTester.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.anticipatedtransition; + +import java.time.Instant; +import java.util.List; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.issue.AnticipatedTransitionDto; +import org.sonar.db.issue.AnticipatedTransitionMapper; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; + +public class AnticipatedTransitionDbTester { + + private final DbTester db; + + public AnticipatedTransitionDbTester(DbTester db) { + this.db = db; + } + + public AnticipatedTransitionDto createForIssue(DefaultIssue issue, String userUuid, String filePath) { + var dto = new AnticipatedTransitionDto( + "uuid_" + randomAlphabetic(5), + issue.projectUuid(), + userUuid, + "wontfix", + "comment for transition", + issue.getLine(), + issue.getMessage(), + null, + issue.getRuleKey().rule(), + filePath, + Instant.now().toEpochMilli()); + db.getDbClient().anticipatedTransitionDao().insert(db.getSession(), dto); + db.commit(); + return dto; + } + + public List selectByProjectUuid(String projectUuid) { + try (DbSession session = db.getDbClient().openSession(false)) { + AnticipatedTransitionMapper mapper = session.getMapper(AnticipatedTransitionMapper.class); + return mapper.selectByProjectUuid(projectUuid); + } + } +} 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 f207b8e00da..6832e63e900 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 @@ -255,5 +255,4 @@ public class IssueDbTester { db.getDbClient().issueDao().insertAsNewCodeOnReferenceBranch(db.getSession(), IssueTesting.newCodeReferenceIssue(issue)); db.commit(); } - } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueStorage.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueStorage.java index 22443b3bf7b..d66e3fbfcc5 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueStorage.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueStorage.java @@ -44,7 +44,7 @@ public class IssueStorage { changeDto.setProjectUuid(issue.projectUuid()); mapper.insert(changeDto); } - } else if ((!issue.isNew() || issue.hasAnticipatedTransitions()) && diffs != null) { + } else if ((!issue.isNew() || issue.getAnticipatedTransitionUuid().isPresent()) && diffs != null) { IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), diffs, issue.projectUuid()); changeDto.setUuid(uuidFactory.create()); changeDto.setProjectUuid(issue.projectUuid()); diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueStorageTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueStorageTest.java new file mode 100644 index 00000000000..f25ca62338e --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueStorageTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.issue; + +import java.util.Date; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.Duration; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.issue.DefaultIssueComment; +import org.sonar.core.issue.FieldDiffs; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.issue.IssueChangeDto; +import org.sonar.db.issue.IssueChangeMapper; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class IssueStorageTest { + private final IssueStorage underTest = new IssueStorage(); + private final UuidFactory uuidFactory = mock(UuidFactory.class); + private final IssueChangeMapper issueChangeMapper = mock(IssueChangeMapper.class); + + @Test + public void when_newIssueWithAnticipatedTransitionInserted_twoChangelogCreated() { + when(uuidFactory.create()).thenReturn("uuid"); + + String issueKey = "ABCDE"; + String commentText = "comment for new issue"; + DefaultIssueComment comment = DefaultIssueComment.create(issueKey, "user_uuid", commentText); + comment.setKey("FGHIJ"); + Date date = DateUtils.parseDateTime("2013-05-18T12:00:00+0000"); + + DefaultIssue issue = new DefaultIssue() + .setKey(issueKey) + .setType(RuleType.BUG) + .setNew(true) + .setRuleKey(RuleKey.of("keyRepo", "r:2145")) + .setProjectUuid("projectUuid") + .setComponentUuid("fileUuid") + .setLine(5000) + .setEffort(Duration.create(10L)) + .setResolution("wontfix") + .setStatus("CLOSED") + .setSeverity("BLOCKER") + .addComment(comment) + .setCreationDate(date) + .setUpdateDate(date) + .setCloseDate(date) + .setCurrentChange(new FieldDiffs()) + .setAnticipatedTransitionUuid("anticipatedTransitionUuid"); + + IssueChangeDto mockCreated = mock(IssueChangeDto.class); + IssueChangeDto mockAnticipatedTransition = mock(IssueChangeDto.class); + try (MockedStatic issueChangeDtoMockedStatic = mockStatic(IssueChangeDto.class)) { + issueChangeDtoMockedStatic.when(() -> IssueChangeDto.of(any(DefaultIssueComment.class), anyString())) + .thenReturn(mockCreated); + issueChangeDtoMockedStatic.when(() -> IssueChangeDto.of(anyString(), any(FieldDiffs.class), anyString())) + .thenReturn(mockAnticipatedTransition); + underTest.insertChanges(issueChangeMapper, issue, uuidFactory); + } + verify(issueChangeMapper, times(2)).insert(any(IssueChangeDto.class)); + verify(issueChangeMapper).insert(mockCreated); + verify(issueChangeMapper).insert(mockAnticipatedTransition); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java index 1b4cbab8bd1..754b370fc73 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java @@ -55,6 +55,7 @@ public class AnticipatedTransitionParser { private static List mapBodyToAnticipatedTransitions(List anticipatedTransitions, String userUuid, String projectKey) { return anticipatedTransitions.stream() .map(anticipatedTransition -> new AnticipatedTransition( + null, projectKey, userUuid, RuleKey.parse(anticipatedTransition.ruleKey()), diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionHandlerTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionHandlerTest.java index 6e6e1d4fa47..289a4e63e61 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionHandlerTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionHandlerTest.java @@ -97,6 +97,7 @@ public class AnticipatedTransitionHandlerTest { private AnticipatedTransition populateAnticipatedTransition() { return new AnticipatedTransition( + null, PROJECT_KEY, USER_UUID, RuleKey.of("repo", "squid:S0001"), @@ -107,4 +108,4 @@ public class AnticipatedTransitionHandlerTest { "transition1", "comment1"); } -} \ No newline at end of file +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java index c1e771124d8..4b535916e85 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java @@ -101,6 +101,7 @@ public class AnticipatedTransitionParserTest { private List transitionsExpectedFromTestFile() { return List.of( new AnticipatedTransition( + null, PROJECT_KEY, USER_UUID, RuleKey.parse("squid:S0001"), @@ -111,6 +112,7 @@ public class AnticipatedTransitionParserTest { "wontfix", "comment1"), new AnticipatedTransition( + null, PROJECT_KEY, USER_UUID, RuleKey.parse("squid:S0002"), diff --git a/sonar-core/src/main/java/org/sonar/core/issue/AnticipatedTransition.java b/sonar-core/src/main/java/org/sonar/core/issue/AnticipatedTransition.java index bcb1c5192dc..4df3240ca8d 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/AnticipatedTransition.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/AnticipatedTransition.java @@ -30,6 +30,7 @@ import org.sonar.core.issue.tracking.Trackable; public class AnticipatedTransition implements Trackable { + private final String uuid; private final String projectKey; private final String transition; private final String userUuid; @@ -41,6 +42,7 @@ public class AnticipatedTransition implements Trackable { private final RuleKey ruleKey; public AnticipatedTransition( + @Nullable String uuid, String projectKey, String userUuid, @Nullable RuleKey ruleKey, @@ -50,6 +52,7 @@ public class AnticipatedTransition implements Trackable { @Nullable String lineHash, String transition, @Nullable String comment) { + this.uuid = uuid; this.projectKey = projectKey; this.transition = transition; this.userUuid = userUuid; @@ -139,4 +142,8 @@ public class AnticipatedTransition implements Trackable { public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } + + public String getUuid() { + return uuid; + } } 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 32b5154b0b9..ef83040304e 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 @@ -131,7 +131,7 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. private String ruleDescriptionContextKey = null; - private boolean anticipatedTransitions = false; + private String anticipatedTransitionUuid = null; @Override public String key() { @@ -691,12 +691,12 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure. return this; } - public boolean hasAnticipatedTransitions() { - return anticipatedTransitions; + public Optional getAnticipatedTransitionUuid() { + return Optional.ofNullable(anticipatedTransitionUuid); } - public DefaultIssue setAnticipatedTransitions(boolean anticipatedTransitions) { - this.anticipatedTransitions = anticipatedTransitions; + public DefaultIssue setAnticipatedTransitionUuid(@Nullable String anticipatedTransitionUuid) { + this.anticipatedTransitionUuid = anticipatedTransitionUuid; return this; } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/AnticipatedTransitionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/AnticipatedTransitionTest.java index 4e1be63dfef..fb48beec034 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/AnticipatedTransitionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/AnticipatedTransitionTest.java @@ -53,10 +53,12 @@ public class AnticipatedTransitionTest { Assertions.assertThat(anticipatedTransition.getMessage()).isEqualTo(anticipatedTransition2.getMessage()); Assertions.assertThat(anticipatedTransition.getLineHash()).isEqualTo(anticipatedTransition2.getLineHash()); Assertions.assertThat(anticipatedTransition.getRuleKey()).isEqualTo(anticipatedTransition2.getRuleKey()); + Assertions.assertThat(anticipatedTransition.getUuid()).isEqualTo(anticipatedTransition2.getUuid()); } private AnticipatedTransition getAnticipatedTransition() { return new AnticipatedTransition( + null, "projectKey", "userUuid", RuleKey.parse("rule:key"), 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 2078f8f60e8..39ba4662157 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 @@ -286,13 +286,13 @@ public class DefaultIssueTest { @Test public void issueByDefault_shouldNotHaveAppliedAnticipatedTransitions() { DefaultIssue defaultIssue = new DefaultIssue(); - assertThat(defaultIssue.hasAnticipatedTransitions()).isFalse(); + assertThat(defaultIssue.getAnticipatedTransitionUuid()).isNotPresent(); } @Test public void anticipatedTransitions_WhenSetTrue_shouldReturnTrue() { DefaultIssue defaultIssue = new DefaultIssue(); - defaultIssue.setAnticipatedTransitions(true); - assertThat(defaultIssue.hasAnticipatedTransitions()).isTrue(); + defaultIssue.setAnticipatedTransitionUuid("uuid"); + assertThat(defaultIssue.getAnticipatedTransitionUuid()).isPresent(); } } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/tracking/AnticipatedTransitionTrackerTest.java b/sonar-core/src/test/java/org/sonar/core/issue/tracking/AnticipatedTransitionTrackerTest.java index 017e4b8417a..19542967b3f 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/tracking/AnticipatedTransitionTrackerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/tracking/AnticipatedTransitionTrackerTest.java @@ -92,7 +92,9 @@ public class AnticipatedTransitionTrackerTest { } private AnticipatedTransition getAnticipatedTransition(Integer line, String message, String hash, String ruleKey) { - return new AnticipatedTransition("projectKey", + return new AnticipatedTransition( + null, + "projectKey", "userUuid", RuleKey.parse(ruleKey), message, -- 2.39.5