]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20058 purge old anticipated transitions according to a max age property
authorMatteo Mara <matteo.mara@sonarsource.com>
Wed, 26 Jul 2023 14:49:06 +0000 (16:49 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Aug 2023 20:03:03 +0000 (20:03 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeConfiguration.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeConfigurationTest.java
sonar-core/src/main/java/org/sonar/core/config/PurgeConstants.java
sonar-core/src/main/java/org/sonar/core/config/PurgeProperties.java
sonar-core/src/test/java/org/sonar/core/config/PurgePropertiesTest.java

index 4778f1eb75d5f9e969963ff320a231e4dab66dab..923955f5147708e7220fdaca70ee0e68f46406a2 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.db.purge;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -47,6 +49,7 @@ import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.dialect.Dialect;
 import org.sonar.db.duplication.DuplicationUnitDto;
 import org.sonar.db.entity.EntityDto;
+import org.sonar.db.issue.AnticipatedTransitionDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.newcodeperiod.NewCodePeriodType;
@@ -779,6 +782,28 @@ public class PurgeCommandsIT {
       .extracting(r -> r.getUuid()).containsExactly("uuid2");
   }
 
+  @Test
+  public void deleteAnticipatedTransitions_shouldDeleteAnticipatedTransitionsOlderThanDate() {
+    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto();
+    //dates at the boundary of the deletion threshold set to 30 days
+    Instant okCreationDate = Instant.now().minus(29, ChronoUnit.DAYS);
+    Instant oldCreationDate = Instant.now().minus(31, ChronoUnit.DAYS);
+
+    dbTester.getDbClient().anticipatedTransitionDao().insert(dbTester.getSession(), getAnticipatedTransitionsDto(projectDto.uuid() + "okTransition", projectDto.uuid(), okCreationDate));
+    dbTester.getDbClient().anticipatedTransitionDao().insert(dbTester.getSession(), getAnticipatedTransitionsDto(projectDto.uuid() + "oldTransition", projectDto.uuid(), oldCreationDate));
+
+    underTest.deleteAnticipatedTransitions(projectDto.uuid(), Instant.now().minus(30, ChronoUnit.DAYS).toEpochMilli());
+
+    List<AnticipatedTransitionDto> anticipatedTransitionDtos = dbTester.getDbClient().anticipatedTransitionDao().selectByProjectUuid(dbTester.getSession(), projectDto.uuid());
+
+    assertThat(anticipatedTransitionDtos).hasSize(1);
+    assertThat(anticipatedTransitionDtos.get(0).getUuid()).isEqualTo(projectDto.uuid() + "okTransition");
+  }
+
+  private AnticipatedTransitionDto getAnticipatedTransitionsDto(String uuid, String projectUuid, Instant creationDate) {
+    return new AnticipatedTransitionDto(uuid, projectUuid, "userUuid", "transition", null, null, null, null, "rule:key", "filePath", creationDate.toEpochMilli());
+  }
+
   private void addPermissions(EntityDto projectDto) {
     if (!projectDto.isPrivate()) {
       dbTester.users().insertEntityPermissionOnAnyone("foo1", projectDto);
index b8182de0987a711c3782e0c4e415435ecb2b52f8..1cf200413ee31b5bc04a800fe0467cc43d1b8a31 100644 (file)
@@ -75,6 +75,7 @@ import org.sonar.db.dialect.Dialect;
 import org.sonar.db.event.EventComponentChangeDto;
 import org.sonar.db.event.EventDto;
 import org.sonar.db.event.EventTesting;
+import org.sonar.db.issue.AnticipatedTransitionDto;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.measure.LiveMeasureDto;
@@ -1834,6 +1835,31 @@ public class PurgeDaoIT {
     assertThat(scannerContextExists("RECENT")).isTrue();
   }
 
+  @Test
+  public void purge_old_anticipated_transitions() {
+    ProjectData project = db.components().insertPrivateProject();
+
+    //dates at the boundary of the deletion threshold set to 30 days
+    Date okCreationDate = DateUtils.addDays(new Date(), -29);
+    Date oldCreationDate = DateUtils.addDays(new Date(), -31);
+
+    dbClient.anticipatedTransitionDao().insert(dbSession, getAnticipatedTransitionsDto("okTransition", project.projectUuid(), okCreationDate));
+    dbClient.anticipatedTransitionDao().insert(dbSession, getAnticipatedTransitionsDto("oldTransition", project.projectUuid(), oldCreationDate));
+
+    underTest.purge(dbSession, newConfigurationWith30Days(System2.INSTANCE, project.getMainBranchComponent().uuid(), project.projectUuid()), PurgeListener.EMPTY,
+      new PurgeProfiler());
+    dbSession.commit();
+
+    List<AnticipatedTransitionDto> anticipatedTransitionDtos = dbClient.anticipatedTransitionDao().selectByProjectUuid(dbSession, project.projectUuid());
+
+    assertThat(anticipatedTransitionDtos).hasSize(1);
+    assertThat(anticipatedTransitionDtos.get(0).getUuid()).isEqualTo("okTransition");
+  }
+
+  private AnticipatedTransitionDto getAnticipatedTransitionsDto(String uuid, String projectUuid, Date creationDate) {
+    return new AnticipatedTransitionDto(uuid, projectUuid, "userUuid", "transition", null, null, null, null, "rule:key", "filepath", creationDate.getTime());
+  }
+
   private Optional<CeActivityDto> selectActivity(String taskUuid) {
     return db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), taskUuid);
   }
@@ -2011,7 +2037,7 @@ public class PurgeDaoIT {
   }
 
   private static PurgeConfiguration newConfigurationWith30Days(System2 system2, String rootUuid, String projectUuid, Set<String> disabledComponentUuids) {
-    return new PurgeConfiguration(rootUuid, projectUuid, 30, Optional.of(30), system2, disabledComponentUuids);
+    return new PurgeConfiguration(rootUuid, projectUuid, 30, Optional.of(30), system2, disabledComponentUuids, 30);
   }
 
   private Stream<String> uuidsOfAnalysesOfRoot(ComponentDto rootComponent) {
index fca4f6b9805a7815eda76a5703268309b99b52c7..610bb1868c0bfd5761c9bd0188929bbbde06f7f8 100644 (file)
@@ -489,7 +489,6 @@ class PurgeCommands {
     profiler.stop();
   }
 
-
   public void deleteReportSubscriptions(String rootUuid) {
     profiler.start("deleteReportSubscriptions (report_subscriptions)");
     purgeMapper.deleteReportSubscriptionsByBranchUuid(rootUuid);
@@ -497,5 +496,10 @@ class PurgeCommands {
     profiler.stop();
   }
 
-
+  public void deleteAnticipatedTransitions(String projectUuid, long createdAt) {
+    profiler.start("deleteAnticipatedTransitions (anticipated_transitions)");
+    purgeMapper.deleteAnticipatedTransitionsByProjectUuidAndCreationDate(projectUuid, createdAt);
+    session.commit();
+    profiler.stop();
+  }
 }
index 786ae8b349c8cab61e0fce9f3488e17fc669a2db..e6ccc9766445fb18ca99fd615354f78579412644 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.db.purge;
 
 import com.google.common.annotations.VisibleForTesting;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.Optional;
 import java.util.Set;
@@ -38,19 +40,23 @@ public class PurgeConfiguration {
   private final System2 system2;
   private final Set<String> disabledComponentUuids;
 
+  private final int maxAgeInDaysOfAnticipatedTransitions;
+
   public PurgeConfiguration(String rootUuid, String projectUuid, int maxAgeInDaysOfClosedIssues,
-    Optional<Integer> maxAgeInDaysOfInactiveBranches, System2 system2, Set<String> disabledComponentUuids) {
+    Optional<Integer> maxAgeInDaysOfInactiveBranches, System2 system2, Set<String> disabledComponentUuids, int maxAgeInDaysOfAnticipatedTransitions) {
     this.rootUuid = rootUuid;
     this.projectUuid = projectUuid;
     this.maxAgeInDaysOfClosedIssues = maxAgeInDaysOfClosedIssues;
     this.system2 = system2;
     this.disabledComponentUuids = disabledComponentUuids;
     this.maxAgeInDaysOfInactiveBranches = maxAgeInDaysOfInactiveBranches;
+    this.maxAgeInDaysOfAnticipatedTransitions = maxAgeInDaysOfAnticipatedTransitions;
   }
 
   public static PurgeConfiguration newDefaultPurgeConfiguration(Configuration config, String rootUuid, String projectUuid, Set<String> disabledComponentUuids) {
     return new PurgeConfiguration(rootUuid, projectUuid, config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_CLOSED_ISSUES).get(),
-      config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS), System2.INSTANCE, disabledComponentUuids);
+      config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS), System2.INSTANCE, disabledComponentUuids,
+      config.getInt(PurgeConstants.DAYS_BEFORE_DELETING_ANTICIPATED_TRANSITIONS).get());
   }
 
   /**
@@ -82,6 +88,11 @@ public class PurgeConfiguration {
     return maxAgeInDaysOfInactiveBranches.map(age -> DateUtils.addDays(new Date(system2.now()), -age));
   }
 
+  public Instant maxLiveDateOfAnticipatedTransitions() {
+    return Instant.ofEpochMilli(system2.now()).minus(maxAgeInDaysOfAnticipatedTransitions, ChronoUnit.DAYS);
+  }
+
+
   @VisibleForTesting
   @CheckForNull
   Date maxLiveDateOfClosedIssues(Date now) {
index 0e35b7cd53fd74b5d2ed5470fc888bfaf36ab868..9ceba885c7a00265335526cccb12a7f685102206 100644 (file)
@@ -71,9 +71,11 @@ public class PurgeDao implements Dao {
     deleteOrphanIssues(mapper, rootUuid);
     purgeOldCeActivities(session, rootUuid, commands);
     purgeOldCeScannerContexts(session, rootUuid, commands);
+    deleteOldAnticipatedTransitions(commands, conf, conf.projectUuid());
 
     deleteOldDisabledComponents(commands, mapper, rootUuid);
     purgeStaleBranches(commands, conf, mapper, rootUuid);
+
   }
 
   private static void purgeStaleBranches(PurgeCommands commands, PurgeConfiguration conf, PurgeMapper mapper, String rootUuid) {
@@ -113,6 +115,11 @@ public class PurgeDao implements Dao {
     deleteIssues(mapper, issueKeys);
   }
 
+  private static void deleteOldAnticipatedTransitions(PurgeCommands commands, PurgeConfiguration purgeConfiguration, String projectUuid) {
+    LOG.debug("<- Delete Old Anticipated Transitions");
+    commands.deleteAnticipatedTransitions(projectUuid, purgeConfiguration.maxLiveDateOfAnticipatedTransitions().toEpochMilli());
+  }
+
   private static void deleteOldClosedIssues(PurgeConfiguration conf, PurgeMapper mapper, PurgeListener listener) {
     Date toDate = conf.maxLiveDateOfClosedIssues();
     String rootUuid = conf.rootUuid();
index 019033026ee3047d5f8a15b77becb8319cf3fecd..44651d1e8726707bfa067c24f851e4bd73609450 100644 (file)
@@ -184,4 +184,6 @@ public interface PurgeMapper {
   void deleteReportSchedulesByPortfolioUuids(@Param("portfolioUuids") List<String> portfolioUuids);
 
   void deleteReportSubscriptionsByPortfolioUuids(@Param("portfolioUuids") List<String> portfolioUuids);
+
+  void deleteAnticipatedTransitionsByProjectUuidAndCreationDate(@Param("projectUuid") String projectUuid, @Param("createdAtBefore") Long createdAtBefore);
 }
index 770955921ca01a20c562afb4d0744bd155187f40..1e7702bde2226c4649f391eb0a9da608323ae536 100644 (file)
 
   <delete id="deleteReportSchedulesByPortfolioUuids">
     delete from report_schedules where portfolio_uuid in <foreach item="portfolioUuid" index="index" collection="portfolioUuids" open="("
-                                                               separator="," close=")">#{portfolioUuid, jdbcType=VARCHAR}</foreach>
+                                                                  separator="," close=")">#{portfolioUuid, jdbcType=VARCHAR}</foreach>
   </delete>
 
-    <delete id="deleteReportSubscriptionsByPortfolioUuids">
-    delete from report_subscriptions where portfolio_uuid in <foreach item="portfolioUuid" index="index" collection="portfolioUuids" open="("
-                                                               separator="," close=")">#{portfolioUuid, jdbcType=VARCHAR}</foreach>
+  <delete id="deleteReportSubscriptionsByPortfolioUuids">
+    delete from report_subscriptions where portfolio_uuid in <foreach item="portfolioUuid" index="index" collection="portfolioUuids"
+                                                                      open="("
+                                                                      separator="," close=")">#{portfolioUuid, jdbcType=VARCHAR}</foreach>
+  </delete>
+
+  <delete id="deleteAnticipatedTransitionsByProjectUuidAndCreationDate">
+    delete from anticipated_transitions where project_uuid = #{projectUuid,jdbcType=VARCHAR} and created_at &lt; #{createdAtBefore,jdbcType=BIGINT}
   </delete>
 </mapper>
 
index f3c835f0619ea31d2151eeb5809120b81a2674c5..473531372f5d1dee2491379582d5d41a6089ea3d 100644 (file)
  */
 package org.sonar.db.purge;
 
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.Optional;
 import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
 
@@ -31,10 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class PurgeConfigurationTest {
   @Test
   public void should_delete_all_closed_issues() {
-    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 0, Optional.empty(), System2.INSTANCE, emptySet());
+    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 0, Optional.empty(), System2.INSTANCE, emptySet(), 0);
     assertThat(conf.maxLiveDateOfClosedIssues()).isNull();
 
-    conf = new PurgeConfiguration("root", "project", -1, Optional.empty(), System2.INSTANCE, emptySet());
+    conf = new PurgeConfiguration("root", "project", -1, Optional.empty(), System2.INSTANCE, emptySet(), 0);
     assertThat(conf.maxLiveDateOfClosedIssues()).isNull();
   }
 
@@ -42,7 +45,7 @@ public class PurgeConfigurationTest {
   public void should_delete_only_old_closed_issues() {
     Date now = DateUtils.parseDate("2013-05-18");
 
-    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.empty(), System2.INSTANCE, emptySet());
+    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.empty(), System2.INSTANCE, emptySet(), 0);
     Date toDate = conf.maxLiveDateOfClosedIssues(now);
 
     assertThat(toDate.getYear()).isEqualTo(113);// =2013
@@ -52,7 +55,7 @@ public class PurgeConfigurationTest {
 
   @Test
   public void should_have_empty_branch_purge_date() {
-    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.of(10), System2.INSTANCE, emptySet());
+    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.of(10), System2.INSTANCE, emptySet(), 0);
     assertThat(conf.maxLiveDateOfInactiveBranches()).isNotEmpty();
     long tenDaysAgo = DateUtils.addDays(new Date(System2.INSTANCE.now()), -10).getTime();
     assertThat(conf.maxLiveDateOfInactiveBranches().get().getTime()).isBetween(tenDaysAgo - 5000, tenDaysAgo + 5000);
@@ -60,8 +63,22 @@ public class PurgeConfigurationTest {
 
   @Test
   public void should_calculate_branch_purge_date() {
-    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.empty(), System2.INSTANCE, emptySet());
+    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.empty(), System2.INSTANCE, emptySet(), 0);
     assertThat(conf.maxLiveDateOfInactiveBranches()).isEmpty();
   }
 
+  @Test
+  public void should_delete_only_old_anticipated_transitions() {
+    int anticipatedTransitionMaxAge = 30;
+    TestSystem2 system2 = new TestSystem2();
+    system2.setNow(Instant.now().toEpochMilli());
+    PurgeConfiguration conf = new PurgeConfiguration("root", "project", 30, Optional.empty(), system2, emptySet(), anticipatedTransitionMaxAge);
+
+    Instant toDate = conf.maxLiveDateOfAnticipatedTransitions();
+
+    assertThat(toDate)
+      .isBeforeOrEqualTo(Instant.now().minus(30, ChronoUnit.DAYS))
+      .isAfter(Instant.now().minus(31, ChronoUnit.DAYS));
+  }
+
 }
index 9960d91ac702cc93c23c2385b454aeaa2e826abb..3a847552d96d67b47be92e153ec435247357a048 100644 (file)
  */
 package org.sonar.core.config;
 
-public interface PurgeConstants {
+public class PurgeConstants {
+
+  public static final String HOURS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_DAY = "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay";
+  public static final String WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_WEEK = "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek";
+  public static final String WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH = "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByMonth";
+  public static final String WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION = "sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion";
+  public static final String WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS = "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots";
+  public static final String DAYS_BEFORE_DELETING_CLOSED_ISSUES = "sonar.dbcleaner.daysBeforeDeletingClosedIssues";
+  public static final String DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS = "sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs";
+  public static final String BRANCHES_TO_KEEP_WHEN_INACTIVE = "sonar.dbcleaner.branchesToKeepWhenInactive";
+  public static final String AUDIT_HOUSEKEEPING_FREQUENCY = "sonar.dbcleaner.auditHousekeeping";
+  public static final String DAYS_BEFORE_DELETING_ANTICIPATED_TRANSITIONS = "sonar.dbcleaner.daysBeforeDeletingAnticipatedTransitions";
+
+  private PurgeConstants() {
+    //class cannot be instantiated
+  }
 
-  String HOURS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_DAY = "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay";
-  String WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_WEEK = "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek";
-  String WEEKS_BEFORE_KEEPING_ONLY_ONE_SNAPSHOT_BY_MONTH = "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByMonth";
-  String WEEKS_BEFORE_KEEPING_ONLY_ANALYSES_WITH_VERSION = "sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion";
-  String WEEKS_BEFORE_DELETING_ALL_SNAPSHOTS = "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots";
-  String DAYS_BEFORE_DELETING_CLOSED_ISSUES = "sonar.dbcleaner.daysBeforeDeletingClosedIssues";
-  String DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS = "sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs";
-  String BRANCHES_TO_KEEP_WHEN_INACTIVE = "sonar.dbcleaner.branchesToKeepWhenInactive";
-  String AUDIT_HOUSEKEEPING_FREQUENCY = "sonar.dbcleaner.auditHousekeeping";
 }
index 9a4dd79861109bd2f3acbab0a09e79dbe6a84af6..35a730d0f575d10de24a4c61cd8046cecc40a615 100644 (file)
@@ -103,6 +103,17 @@ public final class PurgeProperties {
         .category(CoreProperties.CATEGORY_HOUSEKEEPING)
         .subCategory(CoreProperties.SUBCATEGORY_GENERAL)
         .index(6)
+        .build(),
+
+      PropertyDefinition.builder(PurgeConstants.DAYS_BEFORE_DELETING_ANTICIPATED_TRANSITIONS)
+        .defaultValue("30")
+        .name("Delete anticipated transitions after")
+        .description("Anticipated transitions that have not been applied for more than this number of days will be deleted.")
+        .type(PropertyType.INTEGER)
+        .onQualifiers(Qualifiers.PROJECT)
+        .category(CoreProperties.CATEGORY_HOUSEKEEPING)
+        .subCategory(CoreProperties.SUBCATEGORY_GENERAL)
+        .index(7)
         .build());
   }
 }
index 08f4d5fd7ea68968b3befc64bcab01104c40e626..2a70980b85ebe72ce2bd88cd467f34148771915c 100644 (file)
@@ -27,6 +27,6 @@ public class PurgePropertiesTest {
 
   @Test
   public void shouldGetExtensions() {
-    assertThat(PurgeProperties.all()).hasSize(6);
+    assertThat(PurgeProperties.all()).hasSize(7);
   }
 }