From: Jacek Poreda Date: Wed, 2 Aug 2023 11:41:31 +0000 (+0200) Subject: SONAR-20021 Add impacts support in IssueDto, RuleDto X-Git-Tag: 10.2.0.77647~194 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d9cedb7e0e1bd180cdefdb8ec55325795dd9670c;p=sonarqube.git SONAR-20021 Add impacts support in IssueDto, RuleDto --- diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 896dd650288..092d7bf8791 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -58,6 +58,7 @@ public final class SqTables { "internal_component_props", "internal_properties", "issues", + "issues_impacts", "issue_changes", "live_measures", "metrics", @@ -96,6 +97,7 @@ public final class SqTables { "report_subscriptions", "rules", "rule_desc_sections", + "rules_default_impacts", "rules_parameters", "rules_profiles", "rule_repositories", diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java index 6d6d1f8d4c8..74a267e7b07 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java @@ -31,9 +31,12 @@ import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.Pagination; @@ -101,6 +104,8 @@ public class IssueDaoIT { @Before public void setup() { + int i = db.countSql(db.getSession(), "select count(1) from rules_default_impacts"); + db.rules().insert(RULE.setIsExternal(true)); projectDto = db.components().insertPrivateProject(t -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY)).getMainBranchComponent(); db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY)); @@ -142,7 +147,7 @@ public class IssueDaoIT { IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1); assertThat(issue).usingRecursiveComparison() - .ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute") + .ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute", "impacts", "ruleDefaultImpacts") .isEqualTo(expected); assertThat(issue.parseMessageFormattings()).isEqualTo(MESSAGE_FORMATTING); assertThat(issue.getIssueCreationDate()).isNotNull(); @@ -152,6 +157,18 @@ public class IssueDaoIT { assertThat(issue.getRule()).isEqualTo(RULE.getRuleKey()); assertThat(issue.getCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute()); assertThat(issue.parseLocations()).isNull(); + assertThat(issue.getImpacts()) + .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality) + .containsExactlyInAnyOrder( + tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY), + tuple(Severity.LOW, SoftwareQuality.SECURITY)); + + assertThat(issue.getEffectiveImpacts()) + // impacts from rule + .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH) + // impacts from issue + .containsEntry(SoftwareQuality.RELIABILITY, Severity.MEDIUM) + .containsEntry(SoftwareQuality.SECURITY, Severity.LOW); } @Test @@ -169,8 +186,15 @@ public class IssueDaoIT { prepareTables(); List issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3")); - // results are not ordered, so do not use "containsExactly" - assertThat(issues).extracting("key").containsOnly("I1", "I2"); + + assertThat(issues).extracting(IssueDto::getKey).containsExactlyInAnyOrder("I1", "I2"); + assertThat(issues).filteredOn(issueDto -> issueDto.getKey().equals("I1")) + .extracting(IssueDto::getImpacts) + .flatMap(issueImpactDtos -> issueImpactDtos) + .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality) + .containsExactlyInAnyOrder( + tuple(Severity.MEDIUM, SoftwareQuality.RELIABILITY), + tuple(Severity.LOW, SoftwareQuality.SECURITY)); } @Test @@ -537,6 +561,32 @@ public class IssueDaoIT { assertThat(issue1.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY); } + @Test + public void insert_shouldInsertBatchIssuesWithImpacts() { + ImpactDto impact1 = new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY) + .setSeverity(Severity.HIGH); + ImpactDto impact2 = new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSoftwareQuality(SoftwareQuality.SECURITY) + .setSeverity(Severity.LOW); + IssueDto issue1 = createIssueWithKey(ISSUE_KEY1) + .addImpact(impact1) + .addImpact(impact2); + IssueDto issue2 = createIssueWithKey(ISSUE_KEY2); + underTest.insert(db.getSession(), issue1, issue2); + + List issueDtos = underTest.selectByKeys(db.getSession(), Set.of(ISSUE_KEY1, ISSUE_KEY2)); + assertThat(issueDtos) + .extracting(IssueDto::getKey) + .containsExactlyInAnyOrder(ISSUE_KEY1, ISSUE_KEY2); + assertThat(issueDtos).filteredOn(issueDto -> issueDto.getKey().equals(ISSUE_KEY1)) + .flatExtracting(IssueDto::getImpacts) + .containsExactlyInAnyOrder(impact1, impact2) + .doesNotContainNull(); + } + @Test public void update_whenUpdatingRuleDescriptionContextKeyToNull_returnsEmptyContextKey() { IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(TEST_CONTEXT_KEY); @@ -783,7 +833,9 @@ public class IssueDaoIT { .setMessage("the message") .setRuleUuid(RULE.getUuid()) .setComponentUuid(FILE_UUID) - .setProjectUuid(PROJECT_UUID)); + .setProjectUuid(PROJECT_UUID) + .addImpact(newIssueImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM)) + .addImpact(newIssueImpact(SoftwareQuality.SECURITY, Severity.LOW))); underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2) .setRuleUuid(RULE.getUuid()) .setComponentUuid(FILE_UUID) @@ -792,6 +844,13 @@ public class IssueDaoIT { db.getSession().commit(); } + private static ImpactDto newIssueImpact(SoftwareQuality softwareQuality, Severity severity) { + return new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSoftwareQuality(softwareQuality) + .setSeverity(severity); + } + private static RuleType randomRuleTypeExceptHotspot() { return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)]; } diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java index 308c4c81c7d..0572e11d34a 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueMapperIT.java @@ -38,6 +38,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; import org.sonar.api.issue.Issue; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; @@ -424,13 +426,10 @@ public class IssueMapperIT { underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler); assertThat(resultHandler.issues) - .hasSize(4) + .hasSize(1) .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get()) - .containsExactly( - tuple(issue.getKey(), changes[2].getChangeData()), - tuple(issue.getKey(), changes[3].getChangeData()), - tuple(issue.getKey(), changes[0].getChangeData()), - tuple(issue.getKey(), changes[1].getChangeData())); + // we are interested only in the latest closed issue change data + .containsExactly(tuple(issue.getKey(), changes[2].getChangeData())); } @Test @@ -450,12 +449,11 @@ public class IssueMapperIT { underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler); assertThat(resultHandler.issues) - .hasSize(3) + .hasSize(1) .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get()) .containsExactly( - tuple(issue.getKey(), changes[2].getChangeData()), - tuple(issue.getKey(), changes[3].getChangeData()), - tuple(issue.getKey(), changes[1].getChangeData())); + // we are interested only in the latest closed issue change data + tuple(issue.getKey(), changes[2].getChangeData())); } private IssueChangeDto insertToClosedDiff(IssueDto issueDto) { @@ -484,7 +482,7 @@ public class IssueMapperIT { } @SafeVarargs - private final IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer... consumers) { + private IssueDto insertNewClosedIssue(ComponentDto component, RuleType ruleType, Consumer... consumers) { RuleDto rule = dbTester.rules().insert(t -> t.setType(ruleType)); return insertNewClosedIssue(component, rule, system2.now(), consumers); } @@ -495,7 +493,7 @@ public class IssueMapperIT { } @SafeVarargs - private final IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer... consumers) { + private IssueDto insertNewClosedIssue(ComponentDto component, RuleDto rule, long issueCloseTime, Consumer... consumers) { IssueDto res = new IssueDto() .setKee(UuidFactoryFast.getInstance().create()) .setRuleUuid(rule.getUuid()) @@ -503,9 +501,17 @@ public class IssueMapperIT { .setComponentUuid(component.uuid()) .setProjectUuid(component.branchUuid()) .setStatus(Issue.STATUS_CLOSED) - .setIssueCloseTime(issueCloseTime); + .setIssueCloseTime(issueCloseTime) + .addImpact(new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSeverity(Severity.HIGH) + .setSoftwareQuality(SoftwareQuality.MAINTAINABILITY)) + .addImpact(new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSeverity(Severity.LOW) + .setSoftwareQuality(SoftwareQuality.SECURITY)); Arrays.asList(consumers).forEach(c -> c.accept(res)); - underTest.insert(res); + dbTester.getDbClient().issueDao().insert(dbSession, res); dbSession.commit(); return res; } diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java index 476ee263589..937dc4d348b 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeCommandsIT.java @@ -37,6 +37,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbTester; @@ -50,6 +52,7 @@ 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.ImpactDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.metric.MetricDto; import org.sonar.db.newcodeperiod.NewCodePeriodType; @@ -565,7 +568,7 @@ public class PurgeCommandsIT { underTest.deleteIssues(projectOrView.uuid()); assertThat(dbTester.countSql("select count(uuid) from new_code_reference_issues where issue_key in (" + - String.join(", ", issueKeys) + ")")).isZero(); + String.join(", ", issueKeys) + ")")).isZero(); } @Test @@ -957,14 +960,14 @@ public class PurgeCommandsIT { @DataProvider public static Object[] projects() { - return new Object[]{ + return new Object[] { ComponentTesting.newPrivateProjectDto(), ComponentTesting.newPublicProjectDto(), }; } @DataProvider public static Object[] views() { - return new Object[]{ + return new Object[] { ComponentTesting.newPortfolio(), ComponentTesting.newApplication() }; } diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java index 1cf200413ee..9d0e6ccb808 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/purge/PurgeDaoIT.java @@ -1226,6 +1226,7 @@ public class PurgeDaoIT { assertThat(db.countRowsOfTable("issue_changes")).isZero(); assertThat(db.countRowsOfTable("new_code_reference_issues")).isZero(); + assertThat(db.countRowsOfTable("issues_impacts")).isZero(); assertThat(db.select("select kee as \"KEE\" from issues")).extracting(i -> i.get("KEE")).containsOnly(issue1.getKey()); } diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java index d705cfdf883..d8a5fb2d4aa 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java @@ -29,11 +29,13 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import org.apache.commons.lang.RandomStringUtils; import org.apache.ibatis.exceptions.PersistenceException; import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; @@ -44,8 +46,10 @@ import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.RowNotFoundException; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.rule.RuleDto.Scope; import static com.google.common.collect.Sets.newHashSet; @@ -56,6 +60,9 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; +import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY; +import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; import static org.sonar.api.rule.RuleStatus.REMOVED; public class RuleDaoIT { @@ -68,12 +75,18 @@ public class RuleDaoIT { @Test public void selectByKey() { - RuleDto ruleDto = db.rules().insert(); + RuleDto ruleDto = db.rules().insert(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW))); assertThat(underTest.selectByKey(db.getSession(), RuleKey.of("foo", "bar"))) .isEmpty(); RuleDto actualRule = underTest.selectByKey(db.getSession(), ruleDto.getKey()).get(); assertEquals(actualRule, ruleDto); + + assertThat(actualRule.getDefaultImpacts()) + .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity) + .containsExactlyInAnyOrder( + tuple(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH), + tuple(SECURITY, org.sonar.api.issue.impact.Severity.LOW)); } @Test @@ -83,7 +96,6 @@ public class RuleDaoIT { assertThat(underTest.selectByKey(db.getSession(), ruleDto.getKey())).isNotEmpty(); } - @Test public void selectByUuid() { RuleDto ruleDto = db.rules().insert(); @@ -153,14 +165,9 @@ public class RuleDaoIT { @Test public void selectOrFailByKey_fails_if_rule_not_found() { - assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND"))) - .isInstanceOf(RowNotFoundException.class) - .hasMessage("Rule with key 'NOT:FOUND' does not exist"); - } - - @Test - public void selectOrFailDefinitionByKey_fails_if_rule_not_found() { - assertThatThrownBy(() -> underTest.selectOrFailByKey(db.getSession(), RuleKey.of("NOT", "FOUND"))) + DbSession session = db.getSession(); + RuleKey ruleKey = RuleKey.of("NOT", "FOUND"); + assertThatThrownBy(() -> underTest.selectOrFailByKey(session, ruleKey)) .isInstanceOf(RowNotFoundException.class) .hasMessage("Rule with key 'NOT:FOUND' does not exist"); } @@ -192,13 +199,40 @@ public class RuleDaoIT { @Test public void selectAll() { - RuleDto rule1 = db.rules().insertRule(); - RuleDto rule2 = db.rules().insertRule(); + RuleDto rule1 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.LOW))); + RuleDto rule2 = db.rules().insertRule(r -> r.addDefaultImpact(newRuleDefaultImpact(RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))); RuleDto rule3 = db.rules().insertRule(); - assertThat(underTest.selectAll(db.getSession())) + List ruleDtos = underTest.selectAll(db.getSession()); + assertThat(ruleDtos) .extracting(RuleDto::getUuid) - .containsOnly(rule1.getUuid(), rule2.getUuid(), rule3.getUuid()); + .containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(), rule3.getUuid()); + + assertThat(ruleDtos) + .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule1.getUuid())) + .extracting(RuleDto::getDefaultImpacts) + .flatMap(Function.identity()) + .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality) + .containsExactlyInAnyOrder( + tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY), + tuple(org.sonar.api.issue.impact.Severity.LOW, SECURITY)); + + assertThat(ruleDtos) + .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule2.getUuid())) + .extracting(RuleDto::getDefaultImpacts) + .flatMap(Function.identity()) + .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality) + .containsExactlyInAnyOrder( + tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY), + tuple(org.sonar.api.issue.impact.Severity.MEDIUM, RELIABILITY)); + + assertThat(ruleDtos) + .filteredOn(ruleDto -> ruleDto.getUuid().equals(rule3.getUuid())) + .extracting(RuleDto::getDefaultImpacts) + .flatMap(Function.identity()) + .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality) + .containsExactlyInAnyOrder( + tuple(org.sonar.api.issue.impact.Severity.HIGH, MAINTAINABILITY)); } private void assertEquals(RuleDto actual, RuleDto expected) { @@ -313,8 +347,7 @@ public class RuleDaoIT { .extracting(RuleDto::getUuid, RuleDto::getLanguage, RuleDto::getType) .containsExactlyInAnyOrder( tuple(rule1.getUuid(), "java", RuleType.VULNERABILITY.getDbConstant()), - tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant()) - ); + tuple(rule3.getUuid(), "java", RuleType.BUG.getDbConstant())); assertThat(underTest.selectByLanguage(db.getSession(), "js")).hasSize(1); @@ -436,6 +469,7 @@ public class RuleDaoIT { @Test public void insert() { RuleDescriptionSectionDto sectionDto = createDefaultRuleDescriptionSection(); + ImpactDto ruleDefaultImpactDto = newRuleDefaultImpact(SECURITY, org.sonar.api.issue.impact.Severity.HIGH); RuleDto newRule = new RuleDto() .setUuid("rule-uuid") .setRuleKey("NewRuleKey") @@ -443,6 +477,7 @@ public class RuleDaoIT { .setName("new name") .setDescriptionFormat(RuleDto.Format.MARKDOWN) .addRuleDescriptionSectionDto(sectionDto) + .addDefaultImpact(ruleDefaultImpactDto) .setStatus(RuleStatus.DEPRECATED) .setConfigKey("NewConfigKey") .setSeverity(Severity.INFO) @@ -492,6 +527,8 @@ public class RuleDaoIT { assertThat(ruleDto.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator() .containsOnly(sectionDto); assertThat(ruleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR); + assertThat(ruleDto.getDefaultImpacts()).usingRecursiveFieldByFieldElementComparator() + .containsOnly(ruleDefaultImpactDto); } @Test @@ -1148,6 +1185,13 @@ public class RuleDaoIT { .isInstanceOf(PersistenceException.class); } + private static ImpactDto newRuleDefaultImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) { + return new ImpactDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setSoftwareQuality(softwareQuality) + .setSeverity(severity); + } + private static RuleDescriptionSectionDto createDefaultRuleDescriptionSection() { return RuleDescriptionSectionDto.createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), RandomStringUtils.randomAlphanumeric(1000)); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 4d670509d67..acfd0374e1f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -72,6 +72,7 @@ import org.sonar.db.event.EventDto; import org.sonar.db.event.EventMapper; import org.sonar.db.issue.AnticipatedTransitionDto; import org.sonar.db.issue.AnticipatedTransitionMapper; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.IssueChangeDto; import org.sonar.db.issue.IssueChangeMapper; import org.sonar.db.issue.IssueDto; @@ -212,6 +213,7 @@ public class MyBatis { confBuilder.loadAlias("InternalComponentProperty", InternalComponentPropertyDto.class); confBuilder.loadAlias("IssueChange", IssueChangeDto.class); confBuilder.loadAlias("KeyLongValue", KeyLongValue.class); + confBuilder.loadAlias("Impact", ImpactDto.class); confBuilder.loadAlias("Issue", IssueDto.class); confBuilder.loadAlias("NewCodeReferenceIssue", NewCodeReferenceIssueDto.class); confBuilder.loadAlias("Measure", MeasureDto.class); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java new file mode 100644 index 00000000000..b62469b68db --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java @@ -0,0 +1,83 @@ +/* + * 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.issue; + +import java.io.Serializable; +import java.util.Objects; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; + +public class ImpactDto implements Serializable { + private String uuid; + private SoftwareQuality softwareQuality; + private Severity severity; + + public ImpactDto() { + // nothing to do + } + + public String getUuid() { + return uuid; + } + + public ImpactDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public SoftwareQuality getSoftwareQuality() { + return softwareQuality; + } + + public ImpactDto setSoftwareQuality(SoftwareQuality softwareQuality) { + this.softwareQuality = softwareQuality; + return this; + } + + public Severity getSeverity() { + return severity; + } + + public ImpactDto setSeverity(Severity severity) { + this.severity = severity; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ImpactDto impactDto = (ImpactDto) o; + return Objects.equals(uuid, impactDto.uuid) + && Objects.equals(softwareQuality, impactDto.softwareQuality) + && Objects.equals(severity, impactDto.severity); + } + + @Override + public int hashCode() { + return Objects.hash(uuid, softwareQuality, severity); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java index 4ff0349eef9..905c0aa70c9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java @@ -93,13 +93,23 @@ public class IssueDao implements Dao { public void insert(DbSession session, IssueDto dto) { mapper(session).insert(dto); + updateIssueImpacts(dto, mapper(session)); + } + + private static void updateIssueImpacts(IssueDto issueDto, IssueMapper mapper) { + mapper.deleteIssueImpacts(issueDto.getKey()); + insertInsertIssueImpacts(issueDto, mapper); + } + + private static void insertInsertIssueImpacts(IssueDto issueDto, IssueMapper mapper) { + issueDto.getImpacts() + .forEach(impact -> mapper.insertIssueImpact(issueDto.getKey(), impact)); } public void insert(DbSession session, IssueDto dto, IssueDto... others) { - IssueMapper mapper = mapper(session); - mapper.insert(dto); + insert(session, dto); for (IssueDto other : others) { - mapper.insert(other); + insert(session, other); } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java index 0cd237eaedb..af25c60b5ee 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java @@ -26,13 +26,20 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; import java.io.Serializable; import java.util.Collection; +import java.util.Collections; import java.util.Date; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; @@ -43,6 +50,7 @@ import org.sonar.db.protobuf.DbIssues; import org.sonar.db.rule.RuleDto; import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; import static org.sonar.api.utils.DateUtils.dateToLong; import static org.sonar.api.utils.DateUtils.longToDate; @@ -103,6 +111,9 @@ public final class IssueDto implements Serializable { // populate only when retrieving closed issue for issue tracking private String closedChangeData; + private Set impacts = new HashSet<>(); + private Set ruleDefaultImpacts = new HashSet<>(); + public IssueDto() { // nothing to do } @@ -293,7 +304,7 @@ public final class IssueDto implements Serializable { try { return DbIssues.MessageFormattings.parseFrom(messageFormattings); } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException(String.format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e); + throw new IllegalStateException(format("Fail to read ISSUES.MESSAGE_FORMATTINGS [KEE=%s]", kee), e); } } return null; @@ -695,7 +706,7 @@ public final class IssueDto implements Serializable { try { return DbIssues.Locations.parseFrom(locations); } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException(String.format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e); + throw new IllegalStateException(format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e); } } return null; @@ -769,6 +780,51 @@ public final class IssueDto implements Serializable { return this; } + /** + * Return impacts defined on this issue. + * + * @return Collection of impacts + */ + public Set getImpacts() { + return impacts; + } + + public IssueDto addImpact(ImpactDto impact) { + impacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == impact.getSoftwareQuality()).findFirst() + .ifPresent(impactDto -> { + throw new IllegalStateException(format("Impact already defined on issue for Software Quality [%s]", impact.getSoftwareQuality())); + }); + + impacts.add(impact); + return this; + } + + public IssueDto replaceAllImpacts(Collection newImpacts) { + Set newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet()); + if (newSoftwareQuality.size() != newImpacts.size()) { + throw new IllegalStateException("Impacts must have unique Software Quality values"); + } + impacts.clear(); + impacts.addAll(newImpacts); + return this; + } + + Set getRuleDefaultImpacts() { + return ruleDefaultImpacts; + } + + /** + * Returns effective impacts defined on this issue along with default ones. + * + * @return Unmodifiable Map of impacts + */ + public Map getEffectiveImpacts() { + EnumMap effectiveImpacts = new EnumMap<>(SoftwareQuality.class); + ruleDefaultImpacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity())); + impacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity())); + return Collections.unmodifiableMap(effectiveImpacts); + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java index 5191504426a..579c69c13f6 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java @@ -62,6 +62,8 @@ public interface IssueMapper { void insertAsNewCodeOnReferenceBranch(NewCodeReferenceIssueDto issue); + void insertIssueImpact(@Param("issueKey") String issueKey, @Param("dto") ImpactDto issue); + void deleteAsNewCodeOnReferenceBranch(String issueKey); int updateIfBeforeSelectedDate(IssueDto issue); @@ -74,8 +76,9 @@ public interface IssueMapper { List selectByBranch(@Param("keys") Set keys, @Nullable @Param("changedSince") Long changedSince); - List selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams); List selectIssueKeysByQuery(@Param("query") IssueListQuery issueListQuery, @Param("pagination") Pagination pagination); + + void deleteIssueImpacts(String issueKey); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java index 610bb1868c0..0c086d15e08 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -198,6 +198,11 @@ class PurgeCommands { session.commit(); profiler.stop(); + profiler.start("deleteIssues (issues_impacts)"); + purgeMapper.deleteIssuesImpactsByProjectUuid(rootUuid); + session.commit(); + profiler.stop(); + profiler.start("deleteIssues (issues)"); purgeMapper.deleteIssuesByProjectUuid(rootUuid); session.commit(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java index 9ceba885c7a..a1bcec6137e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java @@ -139,6 +139,11 @@ public class PurgeDao implements Dao { return emptyList(); }); + executeLargeInputs(issueKeys, input -> { + mapper.deleteIssuesImpactsFromKeys(input); + return emptyList(); + }); + executeLargeInputs(issueKeys, input -> { mapper.deleteIssuesFromKeys(input); return emptyList(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java index 44651d1e872..9f702aec832 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -92,6 +92,8 @@ public interface PurgeMapper { void deleteNewCodeReferenceIssuesByProjectUuid(@Param("projectUuid") String projectUuid); + void deleteIssuesImpactsByProjectUuid(@Param("projectUuid") String projectUuid); + List selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate); List selectStaleBranchesAndPullRequests(@Param("projectUuid") String projectUuid, @Param("toDate") Long toDate); @@ -107,6 +109,8 @@ public interface PurgeMapper { void deleteNewCodeReferenceIssuesFromKeys(@Param("issueKeys") List keys); + void deleteIssuesImpactsFromKeys(@Param("issueKeys") List keys); + void deleteFileSourcesByProjectUuid(String rootProjectUuid); void deleteFileSourcesByFileUuid(@Param("fileUuids") List fileUuids); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java index 8a332eeba65..1af002cc859 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java @@ -102,12 +102,14 @@ public class RuleDao implements Dao { RuleMapper mapper = mapper(session); mapper.insertRule(ruleDto); updateRuleDescriptionSectionDtos(ruleDto, mapper); + updateRuleDefaultImpacts(ruleDto, mapper); } public void update(DbSession session, RuleDto ruleDto) { RuleMapper mapper = mapper(session); mapper.updateRule(ruleDto); updateRuleDescriptionSectionDtos(ruleDto, mapper); + updateRuleDefaultImpacts(ruleDto, mapper); } private static void updateRuleDescriptionSectionDtos(RuleDto ruleDto, RuleMapper mapper) { @@ -120,6 +122,16 @@ public class RuleDao implements Dao { .forEach(section -> mapper.insertRuleDescriptionSection(ruleDto.getUuid(), section)); } + private static void updateRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) { + mapper.deleteRuleDefaultImpacts(ruleDto.getUuid()); + insertRuleDefaultImpacts(ruleDto, mapper); + } + + private static void insertRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) { + ruleDto.getDefaultImpacts() + .forEach(section -> mapper.insertRuleDefaultImpact(ruleDto.getUuid(), section)); + } + public void scrollIndexingRuleExtensionsByIds(DbSession dbSession, Collection ruleExtensionIds, Consumer consumer) { RuleMapper mapper = mapper(dbSession); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java index ba3d01d4aa7..b01a4220169 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java @@ -27,16 +27,20 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.HashCodeBuilder; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; +import org.sonar.db.issue.ImpactDto; import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptySet; import static java.util.Optional.ofNullable; @@ -68,6 +72,9 @@ public class RuleDto { */ private RuleDto.Format descriptionFormat = null; private RuleStatus status = null; + + private Set defaultImpacts = new HashSet<>(); + private String name = null; private String configKey = null; @@ -267,6 +274,29 @@ public class RuleDto { return this; } + public Set getDefaultImpacts() { + return defaultImpacts; + } + + public RuleDto addDefaultImpact(ImpactDto defaultImpactDto) { + defaultImpacts.stream().filter(impactDto -> impactDto.getSoftwareQuality() == defaultImpactDto.getSoftwareQuality()).findFirst() + .ifPresent(impactDto -> { + throw new IllegalStateException(format("Impact already defined on rule for Software Quality [%s]", defaultImpactDto.getSoftwareQuality())); + }); + defaultImpacts.add(defaultImpactDto); + return this; + } + + public RuleDto replaceAllDefaultImpacts(Collection newImpacts) { + Set newSoftwareQuality = newImpacts.stream().map(ImpactDto::getSoftwareQuality).collect(Collectors.toSet()); + if (newSoftwareQuality.size() != newImpacts.size()) { + throw new IllegalStateException("Impacts must have unique Software Quality values"); + } + defaultImpacts.clear(); + defaultImpacts.addAll(newImpacts); + return this; + } + public String getName() { return name; } @@ -424,7 +454,6 @@ public class RuleDto { return this; } - @CheckForNull public String getDefRemediationFunction() { return defRemediationFunction; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java index 1deca591f7d..5595860cad9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.ibatis.annotations.Param; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleQuery; +import org.sonar.db.issue.ImpactDto; public interface RuleMapper { @@ -52,6 +53,8 @@ public interface RuleMapper { void insertRuleDescriptionSection(@Param("ruleUuid") String ruleUuid, @Param("dto") RuleDescriptionSectionDto ruleDescriptionSectionDto); + void insertRuleDefaultImpact(@Param("ruleUuid") String ruleUuid, @Param("dto") ImpactDto ruleDefaultImpactDto); + void updateRule(RuleDto ruleDefinitionDto); void deleteRuleDescriptionSection(String ruleUuid); @@ -77,4 +80,6 @@ public interface RuleMapper { void deleteDeprecatedRuleKeys(@Param("uuids") List uuids); void insertDeprecatedRuleKey(DeprecatedRuleKeyDto deprecatedRuleKeyDto); + + void deleteRuleDefaultImpacts(String ruleUuid); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml index 0a6bb0f7a41..e1966bf9aa6 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml @@ -41,6 +41,8 @@ i.issue_type as type, i.quick_fix_available as quickFixAvailable, i.code_variants as codeVariantsString, + + @@ -109,6 +111,34 @@ + + ii.uuid as "ii_uuid", + ii.software_quality as "ii_softwareQuality", + ii.severity as "ii_severity", + + + rdi.uuid as "rdi_uuid", + rdi.software_quality as "rdi_softwareQuality", + rdi.severity as "rdi_severity", + + + + + + + + + + + + + + + + + INSERT INTO issues (kee, rule_uuid, severity, manual_severity, message, message_formattings, line, locations, gap, effort, status, tags, rule_description_context_key, @@ -225,7 +255,7 @@ where kee = #{kee} and updated_at <= #{selectedAt} - select , u.login as assigneeLogin @@ -240,8 +270,7 @@ where i.kee=#{kee,jdbcType=VARCHAR} - select from issues i @@ -254,9 +283,11 @@ where i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and i.status <> 'CLOSED' + order by + i.kee - select , ic.change_data as closedChangeData @@ -292,7 +323,7 @@ where i.project_uuid=#{projectUuid,jdbcType=VARCHAR} and i.status <> 'CLOSED' - select from issues i @@ -308,7 +339,7 @@ - select from issues i @@ -617,7 +648,7 @@ order by i.rn asc - select , u.login as assigneeLogin @@ -634,7 +665,7 @@ limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER} - select , u.login as assigneeLogin @@ -656,7 +687,7 @@ left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid - select , u.login as assigneeLogin diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml index 1e7702bde22..e29021fe997 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml @@ -291,6 +291,18 @@ ) + + delete from issues_impacts + where + issue_key in ( + select + kee + from issues + where + project_uuid = #{projectUuid,jdbcType=VARCHAR} + ) + + delete from file_sources where project_uuid=#{rootProjectUuid,jdbcType=VARCHAR} @@ -425,6 +437,14 @@ + + DELETE FROM issues_impacts + WHERE issue_key IN + + #{issueKey,jdbcType=VARCHAR} + + + delete from ce_scanner_context where diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml index 953157fac29..e0518e7009d 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml @@ -48,14 +48,25 @@ rds.rule_uuid = r.uuid + + left outer join rules_default_impacts rdi on + rdi.rule_uuid = r.uuid + + + + rdi.uuid as "rdi_uuid", + rdi.rule_uuid as "rdi_ruleUuid", + rdi.software_quality as "rdi_softwareQuality", + rdi.severity as "rdi_severity", + rds.content as "rds_content", rds.uuid as "rds_uuid", rds.kee as "rds_kee", rds.context_key as "rds_contextKey", rds.context_display_name as "rds_contextDisplayName", + r.uuid as "r_uuid", - @@ -65,6 +76,8 @@ from rules r + + order by r.uuid @@ -81,18 +94,23 @@ + + + + + - - @@ -198,12 +226,14 @@ from rules r + where r.status != 'REMOVED' and r.is_external=${_false} and r.is_template=${_false} and r.rule_type in #{type, jdbcType=INTEGER} and r.language in #{language, jdbcType=VARCHAR} + order by r.uuid