]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20061 purge used anticipated_transition
authorSteve Marion <unknown>
Wed, 26 Jul 2023 15:49:52 +0000 (17:49 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Aug 2023 20:03:03 +0000 (20:03 +0000)
22 files changed:
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepository.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AnticipatedTransitionRepositoryImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitor.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStep.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/util/cache/ProtobufIssueDiskCache.java
server/sonar-ce-task-projectanalysis/src/main/protobuf/issue_cache.proto
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TransitionIssuesToAnticipatedStatesVisitorTest.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/anticipatedtransition/AnticipatedTransitionDbTester.java [new file with mode: 0644]
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/IssueStorage.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueStorageTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionHandlerTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java
sonar-core/src/main/java/org/sonar/core/issue/AnticipatedTransition.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-core/src/test/java/org/sonar/core/issue/AnticipatedTransitionTest.java
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueTest.java
sonar-core/src/test/java/org/sonar/core/issue/tracking/AnticipatedTransitionTrackerTest.java

index 4c97694f98bdc2f0c8eb5a31c1d0f50d33f1065f..77e630aaa390baf17574653ff2eccf26045bdbd2 100644 (file)
@@ -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();
+  }
 }
index e3d904f795e015314c7533bb9ccb23ca8febdf1f..06ac651040f83b974e721e4407e3aa852d8ee7c6 100644 (file)
@@ -25,5 +25,4 @@ import org.sonar.core.issue.AnticipatedTransition;
 
 public interface AnticipatedTransitionRepository {
   Collection<AnticipatedTransition> getAnticipatedTransitionByComponent(Component component);
-
 }
index e2a115a98c2cffe405bfb43064edecadf51578dd..23ab266bb7aecdc17dda9fd03d720f894aca9f67 100644 (file)
@@ -56,12 +56,13 @@ public class AnticipatedTransitionRepositoryImpl implements AnticipatedTransitio
   private Collection<AnticipatedTransition> getAnticipatedTransitions(List<AnticipatedTransitionDto> 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()
     );
   }
-
 }
index 976ab894c2e547af83c5af37db1c8a8371e63007..8cfb9e4e46f68b8029aa99a06a88ee28d25da408 100644 (file)
@@ -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";
index 371691edae84c6542672de604b52ad6fa9ce840c..363b88507d4bc9e22c007ae84bd0a05de088adaa 100644 (file)
@@ -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<DefaultIssue> addedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) {
-    if (addedIssues.isEmpty()) {
-      return;
-    }
+  private void persistNewIssues(IssueStatistics statistics, List<DefaultIssue> 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<DefaultIssue> updatedIssues, IssueMapper mapper, IssueChangeMapper changeMapper) {
index f0f251f95c34ed82117eef04a96c03fb6b8e0b66..69653021a602c931ba3716aa39b266b9a19f236b 100644 (file)
@@ -136,6 +136,9 @@ public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> {
     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<DefaultIssue> {
     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));
index 880424a347b894c902dca48225fb93874346c4fd..c75e6f585092675f60ec10a9c6a1f4d6028f2edd 100644 (file)
@@ -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 {
index 4cd4af882e9f7b141b3c0ca2233d697aec3916be..0517c680c4a9f3a45db36e2e9e9d84a133a5a647 100644 (file)
@@ -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<AnticipatedTransition> 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<AnticipatedTransition> 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) {
index 3eda69945e6481843f5e88998ad9bdf6edaff85f..0b8b34c1fa30dff7dd0f6aa10414f603ce96b4f4 100644 (file)
@@ -49,5 +49,4 @@
     from anticipated_transitions at
     where at.project_uuid=#{projectUuid,jdbcType=VARCHAR} and at.file_path=#{filePath,jdbcType=VARCHAR}
   </select>
-
 </mapper>
index b7f2ce1f3b6e4c11f94e0f0041c58308f1c4e8df..64b76c1c93247e96e3f7c9fbb5e11c339bd4a991 100644 (file)
@@ -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<TestDbImpl> {
   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<TestDbImpl> {
     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<TestDbImpl> {
     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 (file)
index 0000000..1eb8a7c
--- /dev/null
@@ -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<AnticipatedTransitionDto> selectByProjectUuid(String projectUuid) {
+    try (DbSession session = db.getDbClient().openSession(false)) {
+      AnticipatedTransitionMapper mapper = session.getMapper(AnticipatedTransitionMapper.class);
+      return mapper.selectByProjectUuid(projectUuid);
+    }
+  }
+}
index f207b8e00dab7df77d11ac9be75ae57534315b17..6832e63e900407a6a7d541712fdfd2481f1384f1 100644 (file)
@@ -255,5 +255,4 @@ public class IssueDbTester {
     db.getDbClient().issueDao().insertAsNewCodeOnReferenceBranch(db.getSession(), IssueTesting.newCodeReferenceIssue(issue));
     db.commit();
   }
-
 }
index 22443b3bf7b51ac6515012adec5690e83f1d4ddc..d66e3fbfcc52228e84f45597a60c2cb401882779 100644 (file)
@@ -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 (file)
index 0000000..f25ca62
--- /dev/null
@@ -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<IssueChangeDto> 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);
+  }
+}
index 1b4cbab8bd1315a71894a41fcfaa46a1488e475f..754b370fc7375c2a7ec2b5984540b35f7d038872 100644 (file)
@@ -55,6 +55,7 @@ public class AnticipatedTransitionParser {
   private static List<AnticipatedTransition> mapBodyToAnticipatedTransitions(List<GsonAnticipatedTransition> anticipatedTransitions, String userUuid, String projectKey) {
     return anticipatedTransitions.stream()
       .map(anticipatedTransition -> new AnticipatedTransition(
+        null,
         projectKey,
         userUuid,
         RuleKey.parse(anticipatedTransition.ruleKey()),
index 6e6e1d4fa47b1021637052f0ca4a68f6e332d1e7..289a4e63e61f34ce7d1dc479b30e1804de80112d 100644 (file)
@@ -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
+}
index c1e771124d877b107fc72b66eeb8466d6e35f4c1..4b535916e85c6877d8abdba6b499fc08f034eaff 100644 (file)
@@ -101,6 +101,7 @@ public class AnticipatedTransitionParserTest {
   private List<AnticipatedTransition> 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"),
index bcb1c5192dc5bdfb212559298238ae5b8869d030..4df3240ca8db3075ebd6a7d9236a1e957951328a 100644 (file)
@@ -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;
+  }
 }
index 32b5154b0b9859aba37216b8b9b9dcf9814126bc..ef83040304eee4aad390e582b92d71f672b5d3c6 100644 (file)
@@ -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<String> getAnticipatedTransitionUuid() {
+    return Optional.ofNullable(anticipatedTransitionUuid);
   }
 
-  public DefaultIssue setAnticipatedTransitions(boolean anticipatedTransitions) {
-    this.anticipatedTransitions = anticipatedTransitions;
+  public DefaultIssue setAnticipatedTransitionUuid(@Nullable String anticipatedTransitionUuid) {
+    this.anticipatedTransitionUuid = anticipatedTransitionUuid;
     return this;
   }
 
index 4e1be63dfefe0b8e497f5d404d140585e68d6683..fb48beec034a8ddca1bd248011f712c747b28556 100644 (file)
@@ -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"),
index 2078f8f60e822d8cf1c0ed874db2ac733deaf48b..39ba46621574e22d3381cb9fb83c176cb594b8fd 100644 (file)
@@ -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();
   }
 }
index 017e4b8417a459d6451e64b42f4d66717673acb7..19542967b3f4d1ecb97ed72122105b470c512f9a 100644 (file)
@@ -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,