diff options
author | Steve Marion <steve.marion@sonarsource.com> | 2023-10-13 09:22:53 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-10-16 20:02:49 +0000 |
commit | f3b4b50cdceda15c22bc75d578ef81b34c850b7f (patch) | |
tree | 4ef92f75b1eb4ae5973df32937aa73472f82797b /server/sonar-webserver-core | |
parent | 92b69f90be5767fa2d26b7d3e0201be5b29f26bf (diff) | |
download | sonarqube-f3b4b50cdceda15c22bc75d578ef81b34c850b7f.tar.gz sonarqube-f3b4b50cdceda15c22bc75d578ef81b34c850b7f.zip |
SONAR-20672 QualityProfileChange now record the version of SQ the change was done with. Added sq_version field in database. refactor QpChange insertion to batch insert at the same date to improve change grouping in UI. refactor rule impact. add "sonarQubeVersion" parameter to "api/qualityprofile/changelog" endpoint.
Diffstat (limited to 'server/sonar-webserver-core')
4 files changed, 95 insertions, 67 deletions
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java index b1b9c715829..1b1a99c0edd 100644 --- a/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java @@ -52,6 +52,8 @@ import org.sonar.api.server.rule.RuleDescriptionSection; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.Version; +import org.sonar.core.platform.SonarQubeVersion; import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbClient; @@ -149,6 +151,7 @@ public class RulesRegistrantIT { private final DbClient dbClient = db.getDbClient(); private final MetadataIndex metadataIndex = mock(); private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3)); private RuleIndexer ruleIndexer; private ActiveRuleIndexer activeRuleIndexer; @@ -1137,8 +1140,8 @@ public class RulesRegistrantIT { execute(context -> context.createRepository("fake", "java").done()); //THEN List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee())); - assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType) - .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED")); + assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType, QProfileChangeDto::getSqVersion) + .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED", sonarQubeVersion.toString())); } @Test @@ -1162,7 +1165,7 @@ public class RulesRegistrantIT { reset(webServerRuleFinder); RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex, - rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater); + rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater, sonarQubeVersion); task.start(); // Execute a commit to refresh session state as the task is using its own session db.getSession().commit(); diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java index e2dd9e9c3c4..f8ccc6d9369 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java @@ -19,55 +19,59 @@ */ package org.sonar.server.rule.registration; +import com.google.common.collect.Sets; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.core.platform.SonarQubeVersion; import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.qualityprofile.ActiveRuleDto; import org.sonar.db.qualityprofile.QProfileChangeDto; -import org.sonar.db.rule.RuleImpactChangeDto; import org.sonar.db.rule.RuleChangeDto; +import org.sonar.db.rule.RuleImpactChangeDto; import org.sonar.server.rule.PluginRuleUpdate; public class QualityProfileChangesUpdater { private final DbClient dbClient; private final UuidFactory uuidFactory; + private final SonarQubeVersion sonarQubeVersion; - public QualityProfileChangesUpdater(DbClient dbClient, UuidFactory uuidFactory) { + public QualityProfileChangesUpdater(DbClient dbClient, UuidFactory uuidFactory, SonarQubeVersion sonarQubeVersion) { this.dbClient = dbClient; this.uuidFactory = uuidFactory; + this.sonarQubeVersion = sonarQubeVersion; } - public void updateWithoutCommit(DbSession dbSession, Set<PluginRuleUpdate> pluginRuleUpdates) { - for (PluginRuleUpdate pluginRuleUpdate : pluginRuleUpdates) { - String ruleChangeUuid = uuidFactory.create(); - RuleChangeDto ruleChangeDto = createRuleChange(ruleChangeUuid, pluginRuleUpdate); + public void createQprofileChangesForRuleUpdates(DbSession dbSession, Set<PluginRuleUpdate> pluginRuleUpdates) { + List<QProfileChangeDto> changesToPersist = pluginRuleUpdates.stream() + .flatMap(pluginRuleUpdate -> { + RuleChangeDto ruleChangeDto = createNewRuleChange(pluginRuleUpdate); + insertRuleChange(dbSession, ruleChangeDto); - createRuleImpactChanges(ruleChangeUuid, pluginRuleUpdate, ruleChangeDto); - insertRuleChange(dbSession, ruleChangeDto); - - for (String qualityProfileUuid : findQualityProfilesForRule(dbSession, pluginRuleUpdate.getRuleUuid())) { - QProfileChangeDto qProfileChangeDto = new QProfileChangeDto(); - qProfileChangeDto.setUuid(uuidFactory.create()); - qProfileChangeDto.setChangeType("UPDATED"); - qProfileChangeDto.setRuleChange(ruleChangeDto); - qProfileChangeDto.setRulesProfileUuid(qualityProfileUuid); - dbClient.qProfileChangeDao().insert(dbSession, qProfileChangeDto); - } + return findQualityProfilesForRule(dbSession, pluginRuleUpdate.getRuleUuid()).stream() + .map(qualityProfileUuid -> buildQprofileChangeDtoForRuleChange(qualityProfileUuid, ruleChangeDto)); + }).toList(); + if (!changesToPersist.isEmpty()) { + dbClient.qProfileChangeDao().bulkInsert(dbSession, changesToPersist); } } - private static RuleChangeDto createRuleChange(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate) { + private RuleChangeDto createNewRuleChange(PluginRuleUpdate pluginRuleUpdate) { RuleChangeDto ruleChangeDto = new RuleChangeDto(); - ruleChangeDto.setUuid(ruleChangeUuid); + ruleChangeDto.setUuid(uuidFactory.create()); ruleChangeDto.setRuleUuid(pluginRuleUpdate.getRuleUuid()); ruleChangeDto.setOldCleanCodeAttribute(pluginRuleUpdate.getOldCleanCodeAttribute()); ruleChangeDto.setNewCleanCodeAttribute(pluginRuleUpdate.getNewCleanCodeAttribute()); + + ruleChangeDto.setRuleImpactChanges(createRuleImpactChanges(pluginRuleUpdate, ruleChangeDto)); return ruleChangeDto; } @@ -77,46 +81,53 @@ public class QualityProfileChangesUpdater { .map(ActiveRuleDto::getProfileUuid) .collect(Collectors.toSet()); } - private void insertRuleChange(DbSession dbSession, RuleChangeDto ruleChangeDto) { dbClient.ruleChangeDao().insert(dbSession, ruleChangeDto); } - private static void createRuleImpactChanges(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate, RuleChangeDto ruleChangeDto) { - List<SoftwareQuality> matchingSoftwareQualities = pluginRuleUpdate.getMatchingSoftwareQualities(); - for (SoftwareQuality softwareQuality : matchingSoftwareQualities) { + private static Set<RuleImpactChangeDto> createRuleImpactChanges(PluginRuleUpdate pluginRuleUpdate, RuleChangeDto ruleChangeDto) { + Set<RuleImpactChangeDto> ruleImpactChangeDtos = new HashSet<>(); + + pluginRuleUpdate.getMatchingSoftwareQualities().stream() + .map(softwareQuality -> { + RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto(); + ruleImpactChangeDto.setRuleChangeUuid(ruleChangeDto.getUuid()); + ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(softwareQuality)); + ruleImpactChangeDto.setOldSoftwareQuality(softwareQuality); + ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(softwareQuality)); + ruleImpactChangeDto.setNewSoftwareQuality(softwareQuality); + return ruleImpactChangeDto; + }).forEach(ruleImpactChangeDtos::add); + + Iterator<SoftwareQuality> removedIterator = (Sets.difference(pluginRuleUpdate.getOldImpacts().keySet(), pluginRuleUpdate.getMatchingSoftwareQualities())).iterator(); + Iterator<SoftwareQuality> addedIterator = (Sets.difference(pluginRuleUpdate.getNewImpacts().keySet(), pluginRuleUpdate.getMatchingSoftwareQualities())).iterator(); + while (removedIterator.hasNext() || addedIterator.hasNext()) { RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto(); - ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid); - ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(softwareQuality)); - ruleImpactChangeDto.setOldSoftwareQuality(softwareQuality); - ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(softwareQuality)); - ruleImpactChangeDto.setNewSoftwareQuality(softwareQuality); - ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto); - } - - List<SoftwareQuality> oldSoftwareQualities = pluginRuleUpdate.getOldImpacts().keySet() - .stream() - .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList(); - - List<SoftwareQuality> newSoftwareQualities = pluginRuleUpdate.getNewImpacts().keySet() - .stream() - .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList(); - - int size = Math.max(oldSoftwareQualities.size(), newSoftwareQualities.size()); - for(int i = 0; i < size; i++) { - RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto(); - ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid); - if(i < oldSoftwareQualities.size()) { - ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(oldSoftwareQualities.get(i))); - ruleImpactChangeDto.setOldSoftwareQuality(oldSoftwareQualities.get(i)); + ruleImpactChangeDto.setRuleChangeUuid(ruleChangeDto.getUuid()); + if (removedIterator.hasNext()) { + var removedSoftwareQuality = removedIterator.next(); + ruleImpactChangeDto.setOldSoftwareQuality(removedSoftwareQuality); + ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(removedSoftwareQuality)); } - if(i < newSoftwareQualities.size()) { - ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(newSoftwareQualities.get(i))); - ruleImpactChangeDto.setNewSoftwareQuality(newSoftwareQualities.get(i)); + if (addedIterator.hasNext()) { + var addedSoftwareQuality = addedIterator.next(); + ruleImpactChangeDto.setNewSoftwareQuality(addedSoftwareQuality); + ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(addedSoftwareQuality)); } - ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto); + ruleImpactChangeDtos.add(ruleImpactChangeDto); } + return ruleImpactChangeDtos; + } + @NotNull + private QProfileChangeDto buildQprofileChangeDtoForRuleChange(String qualityProfileUuid, RuleChangeDto ruleChangeDto) { + QProfileChangeDto qProfileChangeDto = new QProfileChangeDto(); + qProfileChangeDto.setUuid(uuidFactory.create()); + qProfileChangeDto.setChangeType("UPDATED"); + qProfileChangeDto.setRuleChange(ruleChangeDto); + qProfileChangeDto.setRulesProfileUuid(qualityProfileUuid); + qProfileChangeDto.setSqVersion(sonarQubeVersion.toString()); + return qProfileChangeDto; } } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java index 26575827823..e1f1249954a 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java @@ -39,15 +39,17 @@ import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; +import org.sonar.core.platform.SonarQubeVersion; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.qualityprofile.QProfileChangeDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleRepositoryDto; import org.sonar.server.es.metadata.MetadataIndex; import org.sonar.server.qualityprofile.ActiveRuleChange; import org.sonar.server.qualityprofile.QProfileRules; -import org.sonar.server.rule.PluginRuleUpdate; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; +import org.sonar.server.rule.PluginRuleUpdate; import org.sonar.server.rule.RuleDefinitionsLoader; import org.sonar.server.rule.WebServerRuleFinder; import org.sonar.server.rule.index.RuleIndexer; @@ -77,11 +79,12 @@ public class RulesRegistrant implements Startable { private final StartupRuleUpdater startupRuleUpdater; private final NewRuleCreator newRuleCreator; private final QualityProfileChangesUpdater qualityProfileChangesUpdater; + private final SonarQubeVersion sonarQubeVersion; public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer, ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, WebServerRuleFinder webServerRuleFinder, MetadataIndex metadataIndex, RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater, - NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater) { + NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater, SonarQubeVersion sonarQubeVersion) { this.defLoader = defLoader; this.qProfileRules = qProfileRules; this.dbClient = dbClient; @@ -95,6 +98,7 @@ public class RulesRegistrant implements Startable { this.startupRuleUpdater = startupRuleUpdater; this.newRuleCreator = newRuleCreator; this.qualityProfileChangesUpdater = qualityProfileChangesUpdater; + this.sonarQubeVersion = sonarQubeVersion; } @Override @@ -108,7 +112,7 @@ public class RulesRegistrant implements Startable { for (RulesDefinition.ExtendedRepository repoDef : repositories) { if (languages.get(repoDef.language()) != null) { Set<PluginRuleUpdate> pluginRuleUpdates = registerRules(rulesRegistrationContext, repoDef.rules(), dbSession); - qualityProfileChangesUpdater.updateWithoutCommit(dbSession, pluginRuleUpdates); + qualityProfileChangesUpdater.createQprofileChangesForRuleUpdates(dbSession, pluginRuleUpdates); dbSession.commit(); } } @@ -120,7 +124,13 @@ public class RulesRegistrant implements Startable { // FIXME lack of resiliency, active rules index is corrupted if rule index fails // to be updated. Only a single DB commit should be executed. ruleIndexer.commitAndIndex(dbSession, rulesRegistrationContext.getAllModified().map(RuleDto::getUuid).collect(Collectors.toSet())); - changes.forEach(arChange -> dbClient.qProfileChangeDao().insert(dbSession, arChange.toDto(null))); + + List<QProfileChangeDto> qProfileChangeDtos = changes.stream() + .map(ActiveRuleChange::toSystemChangedDto) + .peek(dto -> dto.setSqVersion(sonarQubeVersion.toString())) + .toList(); + dbClient.qProfileChangeDao().bulkInsert(dbSession, qProfileChangeDtos); + activeRuleIndexer.commitAndIndex(dbSession, changes); rulesRegistrationContext.getRenamed().forEach(e -> LOG.info("Rule {} re-keyed to {}", e.getValue(), e.getKey().getKey())); profiler.stopDebug(); @@ -314,8 +324,8 @@ public class RulesRegistrant implements Startable { private static Set<String> getExistingAndRenamedRepositories(RulesRegistrationContext recorder, Collection<RulesDefinition.Repository> context) { return Stream.concat( - context.stream().map(RulesDefinition.ExtendedRepository::key), - recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository)) + context.stream().map(RulesDefinition.ExtendedRepository::key), + recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository)) .collect(Collectors.toSet()); } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java index aa8fbd01e16..80a37f131fb 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java @@ -27,6 +27,8 @@ import org.mockito.ArgumentCaptor; import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.CleanCodeAttribute; +import org.sonar.api.utils.Version; +import org.sonar.core.platform.SonarQubeVersion; import org.sonar.core.util.UuidFactoryImpl; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -56,8 +58,9 @@ public class QualityProfileChangesUpdaterTest { private final RuleChangeDao ruleChangeDao = mock(); private final QProfileChangeDao qualityProfileChangeDao = mock(); private final ActiveRuleDao activeRuleDao = mock(); + private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3)); - private final QualityProfileChangesUpdater underTest = new QualityProfileChangesUpdater(dbClient, UuidFactoryImpl.INSTANCE); + private final QualityProfileChangesUpdater underTest = new QualityProfileChangesUpdater(dbClient, UuidFactoryImpl.INSTANCE, sonarQubeVersion); @Before public void before() { @@ -68,7 +71,7 @@ public class QualityProfileChangesUpdaterTest { @Test public void updateWithoutCommit_whenNoRuleChanges_thenDontInteractWithDatabase() { - underTest.updateWithoutCommit(mock(), Set.of()); + underTest.createQprofileChangesForRuleUpdates(mock(), Set.of()); verifyNoInteractions(dbClient); } @@ -80,7 +83,7 @@ public class QualityProfileChangesUpdaterTest { pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED); pluginRuleUpdate.setRuleUuid(RULE_UUID); - underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate)); + underTest.createQprofileChangesForRuleUpdates(dbSession, Set.of(pluginRuleUpdate)); verify(ruleChangeDao).insert(argThat(dbSession::equals), argThat(ruleChangeDto -> ruleChangeDto.getNewCleanCodeAttribute() == CleanCodeAttribute.CLEAR @@ -109,7 +112,7 @@ public class QualityProfileChangesUpdaterTest { pluginRuleUpdate2.addNewImpact(SoftwareQuality.SECURITY, Severity.HIGH); pluginRuleUpdate2.addOldImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM); - underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate, pluginRuleUpdate2)); + underTest.createQprofileChangesForRuleUpdates(dbSession, Set.of(pluginRuleUpdate, pluginRuleUpdate2)); ArgumentCaptor<RuleChangeDto> captor = ArgumentCaptor.forClass(RuleChangeDto.class); verify(ruleChangeDao, times(2)).insert(argThat(dbSession::equals), captor.capture()); @@ -146,10 +149,11 @@ public class QualityProfileChangesUpdaterTest { pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED); pluginRuleUpdate.setRuleUuid(RULE_UUID); - underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate)); + underTest.createQprofileChangesForRuleUpdates(dbSession, Set.of(pluginRuleUpdate)); - verify(qualityProfileChangeDao, times(2)).insert(argThat(dbSession::equals), argThat(qProfileChangeDto -> - qProfileChangeDto.getChangeType().equals("UPDATED") - && qProfileChangeDto.getRuleChange() != null)); + verify(qualityProfileChangeDao, times(1)).bulkInsert(argThat(dbSession::equals), + argThat(qProfileChangeDtos -> + qProfileChangeDtos.stream() + .allMatch(dto -> "UPDATED".equals(dto.getChangeType()) && dto.getRuleChange() != null))); } } |