From: Dejan Milisavljevic <130993898+dejan-milisavljevic-sonarsource@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:03:50 +0000 (+0200) Subject: SONAR-22951 Use 5 levels severities for Software Impact X-Git-Tag: 10.7.0.96327~86 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e55e29f6e2632c1eef4db2d659e685a50caa10a6;p=sonarqube.git SONAR-22951 Use 5 levels severities for Software Impact Co-authored-by: Léo Geoffroy Co-authored-by: Stanislav <31501873+stanislavhh@users.noreply.github.com> Co-authored-by: Viktor Vorona Co-authored-by: OrlovAlexander <35396155+OrlovAlexander85@users.noreply.github.com> Co-authored-by: stanislavh --- diff --git a/gradle.properties b/gradle.properties index 435bf57a80c..fe52ff22787 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ version=10.7 # 30 months from the release date for LTA versions # No change required for patch versions versionEOL=2025-03-27 -pluginApiVersion=10.10.0.2391 +pluginApiVersion=10.11.0.2468 description=Open source platform for continuous inspection of code quality projectTitle=SonarQube org.gradle.jvmargs=-Xmx2048m diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java index 9110916977d..9ca57ae518d 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java @@ -96,14 +96,16 @@ public class OneIssuePerLineSensor implements Sensor { return null; } switch (severity) { - case CRITICAL: case BLOCKER: + return org.sonar.api.issue.impact.Severity.BLOCKER; + case CRITICAL: return org.sonar.api.issue.impact.Severity.HIGH; case MAJOR: return org.sonar.api.issue.impact.Severity.MEDIUM; case MINOR: - case INFO: return org.sonar.api.issue.impact.Severity.LOW; + case INFO: + return org.sonar.api.issue.impact.Severity.INFO; default: return null; } diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java index 6468f7ef0d9..4dbd8085f62 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java @@ -127,7 +127,7 @@ public class XooRulesDefinition implements RulesDefinition { NewRule oneIssuePerLine = repo.createRule(OneIssuePerLineSensor.RULE_KEY).setName("One Issue Per Line") .setCleanCodeAttribute(CleanCodeAttribute.COMPLETE) - .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM) + .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.INFO) .setTags("line"); addDescriptionSectionsWithoutContexts(oneIssuePerLine, "Generate an issue on each line of a file. It requires the metric \"lines\"."); addHowToFixSectionsWithContexts(oneIssuePerLine); @@ -138,7 +138,7 @@ public class XooRulesDefinition implements RulesDefinition { NewRule oneQuickFixPerLine = repo.createRule(OneQuickFixPerLineSensor.RULE_KEY).setName("One Quick Fix Per Line") .setCleanCodeAttribute(CleanCodeAttribute.DISTINCT) - .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM) + .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.BLOCKER) .setTags("line"); addAllDescriptionSections(oneQuickFixPerLine, "Generate an issue with quick fix available on each line of a file. It requires the metric \"lines\"."); diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java index 3feed4fa16e..3dae30abdb8 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java @@ -233,6 +233,7 @@ public class ExportIssuesStepIT { .setIssueCreationTime(963L) .setIssueUpdateTime(852L) .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH)) + .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.BLOCKER)) .setIssueCloseTime(741L) .setCodeVariants(List.of("v1", "v2")); @@ -270,7 +271,7 @@ public class ExportIssuesStepIT { assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime()); assertThat(issue.getLocations()).isNotEmpty(); assertThat(issue.getImpactsList()).extracting(ProjectDump.Impact::getSoftwareQuality, ProjectDump.Impact::getSeverity) - .containsOnly(tuple(ProjectDump.SoftwareQuality.MAINTAINABILITY, ProjectDump.Severity.HIGH)); + .containsOnly(tuple(ProjectDump.SoftwareQuality.MAINTAINABILITY, ProjectDump.Severity.HIGH), tuple(ProjectDump.SoftwareQuality.SECURITY, ProjectDump.Severity.BLOCKER)); assertThat(issue.getMessageFormattingsList()) .isEqualTo(ExportIssuesStep.dbToDumpMessageFormatting(messageFormattings.getMessageFormattingList())); assertThat(issue.getCodeVariants()).isEqualTo(issueDto.getCodeVariantsString()); diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java index 0d1ac24b094..94e6af5c4cf 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java @@ -207,7 +207,7 @@ public class ExportAdHocRulesStepIT { .setIsAdHoc(true) .setCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL) .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM)) - .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH)) + .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.BLOCKER)) .setRuleKey(ruleKey) .setScope(RuleDto.Scope.ALL) .setStatus(RuleStatus.READY); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java index 32b15ebaaca..bf7aedad8c2 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java @@ -157,7 +157,7 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter
percent = computePercent(path.current().getHotspotsToReview(), path.current().getHotspotsReviewed()); measureRepository.add(component, newSecurityReviewRatingMetric, Measure.newMeasureBuilder().create(computeRating(percent.orElse(null)).getIndex())); - measureRepository.add(component, newSoftwareQualitySecurityReviewRatingMetric, - Measure.newMeasureBuilder().create(computeAToDRating(percent.orElse(null)).getIndex())); percent.ifPresent(p -> measureRepository.add(component, newSecurityHotspotsReviewedMetric, Measure.newMeasureBuilder().create(p))); if (!path.isRoot()) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java index df9029b0328..c8e8ec02fc0 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java @@ -37,8 +37,6 @@ import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit.FILE; import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; -import static org.sonar.server.security.SecurityReviewRating.computeAToDRating; import static org.sonar.server.security.SecurityReviewRating.computePercent; import static org.sonar.server.security.SecurityReviewRating.computeRating; @@ -47,7 +45,6 @@ public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter percent = computePercent(path.current().getHotspotsToReview(), path.current().getHotspotsReviewed()); measureRepository.add(component, securityReviewRatingMetric, RatingMeasures.get(computeRating(percent.orElse(null)))); - measureRepository.add(component, softwareQualitySecurityReviewRatingMetric, - RatingMeasures.get(computeAToDRating(percent.orElse(null)))); percent.ifPresent(p -> measureRepository.add(component, securityHotspotsReviewedMetric, newMeasureBuilder().create(p, securityHotspotsReviewedMetric.getDecimalScale()))); if (!path.isRoot()) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStep.java index 7ec45800832..42497ea5066 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStep.java @@ -114,7 +114,6 @@ public class PersistLiveMeasuresStep implements ComputationStep { SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, - SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, diff --git a/server/sonar-ce-task-projectanalysis/src/main/protobuf/project_dump.proto b/server/sonar-ce-task-projectanalysis/src/main/protobuf/project_dump.proto index d1e3782b7f1..114fdda60f4 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/protobuf/project_dump.proto +++ b/server/sonar-ce-task-projectanalysis/src/main/protobuf/project_dump.proto @@ -253,4 +253,6 @@ enum Severity { LOW = 0; MEDIUM = 1; HIGH = 2; + INFO = 3; + BLOCKER = 4; } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java index 7ad66ed9b40..f47a6da5495 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java @@ -126,6 +126,7 @@ import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_MET import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_NEW_METRIC_KEY; import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder; import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf; +import static org.sonar.test.JsonAssert.assertJson; class IssueCounterTest { @@ -316,9 +317,11 @@ class IssueCounterTest { underTest.beforeComponent(PROJECT); underTest.afterComponent(PROJECT); - assertIntValue(FILE1, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0), + assertIntValue(FILE1, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), + entry(NEW_MAJOR_VIOLATIONS_KEY, 0), entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1)); - assertIntValue(PROJECT, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0), + assertIntValue(PROJECT, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), + entry(NEW_MAJOR_VIOLATIONS_KEY, 0), entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1)); } @@ -350,14 +353,14 @@ class IssueCounterTest { when(newIssueClassifier.isEnabled()).thenReturn(true); underTest.beforeComponent(FILE1); - underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM)); - underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, HIGH)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, HIGH)); underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, MEDIUM)); underTest.onIssue(FILE1, createNewSecurityHotspot()); @@ -368,9 +371,9 @@ class IssueCounterTest { Set> entries = measureRepository.getRawMeasures(FILE1).entrySet(); - assertOverallSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(4, 2, 2, 0), entries); - assertOverallSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(2, 1, 1, 0), entries); - assertOverallSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(0, 0, 0, 0), entries); + assertOverallSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(4, 2, 2, 0, 0, 0), entries); + assertOverallSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(2, 1, 1, 0, 0, 0), entries); + assertOverallSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(0, 0, 0, 0, 0, 0), entries); } @Test @@ -378,17 +381,17 @@ class IssueCounterTest { when(newIssueClassifier.isEnabled()).thenReturn(true); underTest.beforeComponent(FILE1); - underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.MAINTAINABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM)); - underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, HIGH)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, LOW)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.RELIABILITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, MEDIUM)); - underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, MEDIUM)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, MEDIUM)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, LOW)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, HIGH)); underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, HIGH)); @@ -402,9 +405,9 @@ class IssueCounterTest { Set> entries = measureRepository.getRawMeasures(FILE1).entrySet(); - assertNewSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(2, 1, 1, 0), entries); - assertNewSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(2, 0, 1, 1), entries); - assertNewSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(4, 2, 1, 1), entries); + assertNewSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(2, 1, 1, 0, 0, 0), entries); + assertNewSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(2, 0, 1, 1, 0, 0), entries); + assertNewSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(4, 2, 1, 1, 0, 0), entries); } private static Map getImpactMeasure(long total, long high, long medium, long low) { @@ -416,6 +419,13 @@ class IssueCounterTest { return map; } + private static Map getImpactMeasure(long total, long high, long medium, long low, long info, long blocker) { + Map map = getImpactMeasure(total, high, medium, low); + map.put(Severity.INFO.name(), info); + map.put(Severity.BLOCKER.name(), blocker); + return map; + } + private void assertOverallSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map expectedMap, Set> actualRaw) { assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_METRIC_KEY); @@ -434,7 +444,7 @@ class IssueCounterTest { .findFirst() .get(); - assertThat(softwareQualityMap.getValue().getData()).isEqualTo(new Gson().toJson(expectedMap)); + assertJson(softwareQualityMap.getValue().getData()).isSimilarTo(new Gson().toJson(expectedMap)); } @Test @@ -513,9 +523,11 @@ class IssueCounterTest { underTest.beforeComponent(PROJECT); underTest.afterComponent(PROJECT); - assertIntValue(FILE1, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0), + assertIntValue(FILE1, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), + entry(NEW_MAJOR_VIOLATIONS_KEY, 0), entry(NEW_VULNERABILITIES_KEY, 0)); - assertIntValue(PROJECT, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0), + assertIntValue(PROJECT, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), + entry(NEW_MAJOR_VIOLATIONS_KEY, 0), entry(NEW_VULNERABILITIES_KEY, 0)); } @@ -546,7 +558,8 @@ class IssueCounterTest { return createNewIssue(resolution, status, SoftwareQuality.MAINTAINABILITY, impactSeverity); } - private DefaultIssue createNewIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality, Severity impactSeverity) { + private DefaultIssue createNewIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality, + Severity impactSeverity) { DefaultIssue issue = createNewIssue(resolution, status, MAJOR, CODE_SMELL); issue.addImpact(softwareQuality, impactSeverity); return issue; @@ -566,7 +579,8 @@ class IssueCounterTest { return createIssue(resolution, status, SoftwareQuality.MAINTAINABILITY, impactSeverity); } - private static DefaultIssue createIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality, Severity impactSeverity) { + private static DefaultIssue createIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality, + Severity impactSeverity) { DefaultIssue issue = createIssue(resolution, status, MAJOR, CODE_SMELL); issue.addImpact(softwareQuality, impactSeverity); return issue; diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java index e2741c6a5da..25779ab5480 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java @@ -279,7 +279,7 @@ class MaintainabilityMeasuresVisitorTest { verifyAddedRawMeasure(FILE_1_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C); verifyAddedRawMeasure(FILE_2_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, A); verifyAddedRawMeasure(DIRECTORY_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, C); - verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, D); + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, E); } @ParameterizedTest diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java index 19415757cec..ad891b15259 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java @@ -358,7 +358,7 @@ public class NewMaintainabilityMeasuresVisitorTest { @ParameterizedTest @MethodSource("metrics") - void compute_new_maintainability_rating_map_to_D(String remediationEffortKey, String debtRatioKey, String ratingKey) { + void compute_new_maintainability_rating_map_to_E(String remediationEffortKey, String debtRatioKey, String ratingKey) { ReportComponent file = builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 1)).build(); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) @@ -375,12 +375,7 @@ public class NewMaintainabilityMeasuresVisitorTest { setNewLines(file, 3, 4); underTest.visit(treeRootHolder.getRoot()); - - if (ratingKey.equals(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)) { - assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, D); - } else if (ratingKey.equals(NEW_MAINTAINABILITY_RATING_KEY)) { - assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, E); - } + assertNewRating(ratingKey, LANGUAGE_1_FILE_REF, E); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java index f2d94abce7a..48eba3a7541 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java @@ -186,7 +186,7 @@ class NewReliabilityAndSecurityRatingMeasuresVisitorTest { oldImpactIssue(SoftwareQuality.SECURITY, Severity.HIGH)); fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, newImpactIssue(SoftwareQuality.SECURITY, Severity.LOW), - newImpactIssue(SoftwareQuality.SECURITY, Severity.HIGH), + newImpactIssue(SoftwareQuality.SECURITY, Severity.BLOCKER), // Should not be taken into account oldImpactIssue(SoftwareQuality.SECURITY, Severity.HIGH)); fillComponentIssuesVisitorRule.setIssues(ROOT_DIR_REF, newImpactIssue(SoftwareQuality.SECURITY, Severity.HIGH)); @@ -194,10 +194,10 @@ class NewReliabilityAndSecurityRatingMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, C); - verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, D); - verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, D); - verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, D); - verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, D); + verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); } @Test @@ -289,7 +289,7 @@ class NewReliabilityAndSecurityRatingMeasuresVisitorTest { // Should not be taken into account oldImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, - newImpactIssue(SoftwareQuality.RELIABILITY, Severity.LOW), + newImpactIssue(SoftwareQuality.RELIABILITY, Severity.INFO), newImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH), // Should not be taken into account oldImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); @@ -375,6 +375,21 @@ class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SECURITY_RATING_KEY, E); } + @Test + void compute_E_software_quality_reliability_and_security_rating_on_blocker_severity_issue() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newImpactIssue(SoftwareQuality.RELIABILITY, Severity.BLOCKER), + newImpactIssue(SoftwareQuality.SECURITY, Severity.BLOCKER), + // Should not be taken into account + newCodeSmellIssue(1L, MAJOR)); + + underTest.visit(ROOT_PROJECT); + + verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); + } + @Test void compute_D_reliability_and_security_rating_on_critical_issue() { treeRootHolder.setRoot(ROOT_PROJECT); @@ -480,6 +495,21 @@ class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SECURITY_RATING_KEY, A); } + @Test + void compute_A_software_quality_reliability_and_security_rating_on_info_severity_issue() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newImpactIssue(SoftwareQuality.RELIABILITY, Severity.INFO), + newImpactIssue(SoftwareQuality.SECURITY, Severity.INFO), + // Should not be taken into account + newCodeSmellIssue(1L, MAJOR)); + + underTest.visit(ROOT_PROJECT); + + verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, A); + verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, A); + } + @Test void compute_A_software_quality_reliability_and_security_rating_when_no_issue() { treeRootHolder.setRoot(ROOT_PROJECT); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java index 2fe66648466..83d8db3d25c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java @@ -68,8 +68,6 @@ import static org.sonar.server.measure.Rating.B; import static org.sonar.server.measure.Rating.C; import static org.sonar.server.measure.Rating.D; import static org.sonar.server.measure.Rating.E; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; class NewSecurityReviewMeasuresVisitorTest { private static final Offset VALUE_COMPARISON_OFFSET = Offset.offset(0.01); @@ -98,7 +96,6 @@ class NewSecurityReviewMeasuresVisitorTest { @RegisterExtension private final MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(NEW_SECURITY_REVIEW_RATING) - .add(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) .add(NEW_SECURITY_HOTSPOTS_REVIEWED) .add(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS) .add(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS); @@ -134,11 +131,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, A, 100.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, 100.0); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, 100.0); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, 100.0); } @Test @@ -163,11 +160,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, A, B, 80.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, B, 87.5); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, B, 87.5); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, B, 87.5); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, A, 80.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, 87.5); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, 87.5); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, 87.5); } @Test @@ -192,11 +189,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, B, B, 71.42); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, B, B, 75.0); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, B, B, 75.0); - verifyRatingAndReviewedMeasures(PROJECT_REF, B, B, 75.0); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, B, 71.42); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, B, 75.0); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, B, 75.0); + verifyRatingAndReviewedMeasures(PROJECT_REF, B, 75.0); } @Test @@ -220,11 +217,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, C, C, 50.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, C, C, 60.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, C, C, 57.14); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, C, C, 57.14); - verifyRatingAndReviewedMeasures(PROJECT_REF, C, C, 57.14); + verifyRatingAndReviewedMeasures(FILE_1_REF, C, 50.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, C, 60.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, C, 57.14); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, C, 57.14); + verifyRatingAndReviewedMeasures(PROJECT_REF, C, 57.14); } @Test @@ -249,11 +246,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, D, D, 33.33); - verifyRatingAndReviewedMeasures(FILE_2_REF, D, D, 40.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, D, D, 37.5); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, D, D, 37.5); - verifyRatingAndReviewedMeasures(PROJECT_REF, D, D, 37.5); + verifyRatingAndReviewedMeasures(FILE_1_REF, D, 33.33); + verifyRatingAndReviewedMeasures(FILE_2_REF, D, 40.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, D, 37.5); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, D, 37.5); + verifyRatingAndReviewedMeasures(PROJECT_REF, D, 37.5); } @Test @@ -276,11 +273,11 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, D, D, 33.33); - verifyRatingAndReviewedMeasures(FILE_2_REF, E, D, 0.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, E, D, 16.66); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, E, D, 16.66); - verifyRatingAndReviewedMeasures(PROJECT_REF, E, D, 16.66); + verifyRatingAndReviewedMeasures(FILE_1_REF, D, 33.33); + verifyRatingAndReviewedMeasures(FILE_2_REF, E, 0.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, E, 16.66); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, E, 16.66); + verifyRatingAndReviewedMeasures(PROJECT_REF, E, 16.66); } @Test @@ -293,7 +290,7 @@ class NewSecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, null); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, null); } @Test @@ -343,10 +340,8 @@ class NewSecurityReviewMeasuresVisitorTest { assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).values()).isEmpty(); } - private void verifyRatingAndReviewedMeasures(int componentRef, Rating expectedReviewRating, - Rating expectedSoftwareQualitySecurityReviewRating, @Nullable Double expectedHotspotsReviewed) { + private void verifyRatingAndReviewedMeasures(int componentRef, Rating expectedReviewRating, @Nullable Double expectedHotspotsReviewed) { assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_REVIEW_RATING_KEY)).hasValue(expectedReviewRating.getIndex()); - assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY)).hasValue(expectedSoftwareQualitySecurityReviewRating.getIndex()); if (expectedHotspotsReviewed != null) { assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).hasValue(expectedHotspotsReviewed, VALUE_COMPARISON_OFFSET); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java index fae829957bb..9ce99a9556a 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java @@ -163,7 +163,7 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { // Should not be taken into account newImpactIssue(SoftwareQuality.SECURITY, Severity.HIGH)); - fillComponentIssuesVisitorRule.setIssues(PROJECT_REF, newImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); + fillComponentIssuesVisitorRule.setIssues(PROJECT_REF, newImpactIssue(SoftwareQuality.RELIABILITY, Severity.BLOCKER)); underTest.visit(ROOT_PROJECT); @@ -171,7 +171,7 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasure(FILE_2_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, C); verifyAddedRawMeasure(FILE_3_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasure(DIRECTORY_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, C); - verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, D); + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, E); } @Test @@ -197,13 +197,13 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { void compute_software_quality_security_rating() { treeRootHolder.setRoot(ROOT_PROJECT); fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, - newImpactIssue(SoftwareQuality.SECURITY, Severity.LOW), + newImpactIssue(SoftwareQuality.SECURITY, Severity.INFO), // Should not be taken into account newImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, newImpactIssue(SoftwareQuality.SECURITY, Severity.MEDIUM), // Should not be taken into account - newImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); + newImpactIssue(SoftwareQuality.RELIABILITY, Severity.BLOCKER)); fillComponentIssuesVisitorRule.setIssues(FILE_3_REF, // Should not be taken into account newImpactIssue(SoftwareQuality.RELIABILITY, Severity.HIGH)); @@ -212,7 +212,7 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyAddedRawMeasure(FILE_1_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, B); + verifyAddedRawMeasure(FILE_1_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, A); verifyAddedRawMeasure(FILE_2_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, C); verifyAddedRawMeasure(FILE_3_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, A); verifyAddedRawMeasure(DIRECTORY_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, C); @@ -232,6 +232,19 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasure(PROJECT_REF, SECURITY_RATING_KEY, E); } + @Test + void compute_E_software_quality_reliability_and_security_rating_on_blocker_issue() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, newImpactIssue(SoftwareQuality.RELIABILITY, Severity.BLOCKER), newImpactIssue(SoftwareQuality.SECURITY, Severity.BLOCKER), + // Should not be taken into account + newImpactIssue(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); + + underTest.visit(ROOT_PROJECT); + + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, E); + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, E); + } + @Test void compute_D_reliability_and_security_rating_on_critical_issue() { treeRootHolder.setRoot(ROOT_PROJECT); @@ -323,6 +336,19 @@ class ReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasure(PROJECT_REF, SECURITY_RATING_KEY, A); } + @Test + void compute_A_software_quality_reliability_and_security_rating_on_info_issue() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, newImpactIssue(SoftwareQuality.RELIABILITY, Severity.INFO), newImpactIssue(SoftwareQuality.SECURITY, Severity.INFO), + // Should not be taken into account + newImpactIssue(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); + + underTest.visit(ROOT_PROJECT); + + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, A); + verifyAddedRawMeasure(PROJECT_REF, SOFTWARE_QUALITY_SECURITY_RATING_KEY, A); + } + @Test void compute_A_software_quality_reliability_and_security_rating_when_no_issue() { treeRootHolder.setRoot(ROOT_PROJECT); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitorTest.java index 8d33cc5ddf5..ef5b6b59f8a 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitorTest.java @@ -59,8 +59,6 @@ import static org.sonar.server.measure.Rating.B; import static org.sonar.server.measure.Rating.C; import static org.sonar.server.measure.Rating.D; import static org.sonar.server.measure.Rating.E; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; class SecurityReviewMeasuresVisitorTest { @@ -87,7 +85,6 @@ class SecurityReviewMeasuresVisitorTest { @RegisterExtension private final MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(SECURITY_REVIEW_RATING) - .add(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) .add(SECURITY_HOTSPOTS_REVIEWED) .add(SECURITY_HOTSPOTS_REVIEWED_STATUS) .add(SECURITY_HOTSPOTS_TO_REVIEW_STATUS); @@ -115,11 +112,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, A, 100.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, 100.0); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, 100.0); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, 100.0); } @Test @@ -141,11 +138,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, A, B, 80.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, B, 87.5); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, B, 87.5); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, B, 87.5); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, A, 80.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, A, 87.5); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, A, 87.5); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, 87.5); } @Test @@ -167,11 +164,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, A, A, 100.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, B, B, 71.4); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, B, B, 75.0); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, B, B, 75.0); - verifyRatingAndReviewedMeasures(PROJECT_REF, B, B, 75.0); + verifyRatingAndReviewedMeasures(FILE_1_REF, A, 100.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, B, 71.4); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, B, 75.0); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, B, 75.0); + verifyRatingAndReviewedMeasures(PROJECT_REF, B, 75.0); } @Test @@ -192,11 +189,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, C, C,50.0); - verifyRatingAndReviewedMeasures(FILE_2_REF, C, C,60.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, C,C, 57.1); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, C, C,57.1); - verifyRatingAndReviewedMeasures(PROJECT_REF, C, C,57.1); + verifyRatingAndReviewedMeasures(FILE_1_REF, C, 50.0); + verifyRatingAndReviewedMeasures(FILE_2_REF, C, 60.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, C, 57.1); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, C, 57.1); + verifyRatingAndReviewedMeasures(PROJECT_REF, C, 57.1); } @Test @@ -218,11 +215,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, D, D,33.3); - verifyRatingAndReviewedMeasures(FILE_2_REF, D, D,40.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, D,D, 37.5); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, D, D,37.5); - verifyRatingAndReviewedMeasures(PROJECT_REF, D, D,37.5); + verifyRatingAndReviewedMeasures(FILE_1_REF, D, 33.3); + verifyRatingAndReviewedMeasures(FILE_2_REF, D, 40.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, D, 37.5); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, D, 37.5); + verifyRatingAndReviewedMeasures(PROJECT_REF, D, 37.5); } @Test @@ -242,11 +239,11 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(FILE_1_REF, D, D,33.3); - verifyRatingAndReviewedMeasures(FILE_2_REF, E, D,0.0); - verifyRatingAndReviewedMeasures(DIRECTORY_REF, E,D, 16.7); - verifyRatingAndReviewedMeasures(ROOT_DIR_REF, E, D,16.7); - verifyRatingAndReviewedMeasures(PROJECT_REF, E, D,16.7); + verifyRatingAndReviewedMeasures(FILE_1_REF, D, 33.3); + verifyRatingAndReviewedMeasures(FILE_2_REF, E, 0.0); + verifyRatingAndReviewedMeasures(DIRECTORY_REF, E, 16.7); + verifyRatingAndReviewedMeasures(ROOT_DIR_REF, E, 16.7); + verifyRatingAndReviewedMeasures(PROJECT_REF, E, 16.7); } @Test @@ -255,7 +252,7 @@ class SecurityReviewMeasuresVisitorTest { underTest.visit(ROOT_PROJECT); - verifyRatingAndReviewedMeasures(PROJECT_REF, A, A,null); + verifyRatingAndReviewedMeasures(PROJECT_REF, A, null); } @Test @@ -292,9 +289,8 @@ class SecurityReviewMeasuresVisitorTest { verifyHotspotStatusMeasures(PROJECT_REF, 0, 0); } - private void verifyRatingAndReviewedMeasures(int componentRef, Rating expectedReviewRating, Rating expectedSoftwareQualityReviewRating, - @Nullable Double expectedHotspotsReviewed) { - verifySecurityReviewRating(componentRef, expectedReviewRating, expectedSoftwareQualityReviewRating); + private void verifyRatingAndReviewedMeasures(int componentRef, Rating expectedReviewRating, @Nullable Double expectedHotspotsReviewed) { + verifySecurityReviewRating(componentRef, expectedReviewRating); if (expectedHotspotsReviewed != null) { verifySecurityHotspotsReviewed(componentRef, expectedHotspotsReviewed); } else { @@ -302,13 +298,10 @@ class SecurityReviewMeasuresVisitorTest { } } - private void verifySecurityReviewRating(int componentRef, Rating rating, Rating softwareQualityRating) { + private void verifySecurityReviewRating(int componentRef, Rating rating) { Measure measure = measureRepository.getAddedRawMeasure(componentRef, SECURITY_REVIEW_RATING_KEY).get(); - Measure softwareQualityMeasure = measureRepository.getAddedRawMeasure(componentRef, SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY).get(); assertThat(measure.getIntValue()).isEqualTo(rating.getIndex()); assertThat(measure.getData()).isEqualTo(rating.name()); - assertThat(softwareQualityMeasure.getIntValue()).isEqualTo(softwareQualityRating.getIndex()); - assertThat(softwareQualityMeasure.getData()).isEqualTo(softwareQualityRating.name()); } private void verifySecurityHotspotsReviewed(int componentRef, double percent) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java index f7d898745b0..eb256bd6309 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java @@ -77,9 +77,7 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator i.replaceAllImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH), + new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.INFO)))); underTest.indexAllIssues(); @@ -205,7 +208,10 @@ public class IssueIndexerIT { assertThat(doc.impacts()) .containsExactlyInAnyOrder(Map.of( SUB_FIELD_SOFTWARE_QUALITY, SoftwareQuality.MAINTAINABILITY.name(), - SUB_FIELD_SEVERITY, Severity.HIGH.name())); + SUB_FIELD_SEVERITY, Severity.HIGH.name()), + Map.of( + SUB_FIELD_SOFTWARE_QUALITY, SoftwareQuality.SECURITY.name(), + SUB_FIELD_SEVERITY, Severity.INFO.name())); assertThat(doc.issueStatus()).isEqualTo(issue.getIssueStatus().name()); } diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java index a7be47d330e..87c8474ffe6 100644 --- a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java +++ b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java @@ -50,11 +50,11 @@ import org.sonar.server.es.SearchOptions; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.security.SecurityStandards; -import static com.google.common.collect.ImmutableSet.of; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; +import static java.util.Set.of; import static java.util.stream.IntStream.rangeClosed; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -225,7 +225,8 @@ public class RuleIndexIT { RuleDto rule1 = insertJavaRule("My great rule CWE-123 which makes your code 1000 times better!", "123", "rule 123"); RuleDto rule2 = insertJavaRule("Another great and shiny rule CWE-124", "124", "rule 124"); RuleDto rule3 = insertJavaRule("Another great rule CWE-1000", "1000", "rule 1000"); - RuleDto rule4 = insertJavaRule("

HTML-Geeks

special formatting!

insidetables
", "404", "rule 404"); + RuleDto rule4 = insertJavaRule("

HTML-Geeks

special " + + "formatting!

insidetables
", "404", "rule 404"); RuleDto rule5 = insertJavaRule("internationalization missunderstandings alsdkjfnadklsjfnadkdfnsksdjfn", "405", "rule 405"); index(); @@ -323,7 +324,7 @@ public class RuleIndexIT { @Test public void tags_facet_supports_selected_value_with_regexp_special_characters() { - createRule(r -> r.setTags(Set.of("misra++"))); + createRule(r -> r.setTags(of("misra++"))); index(); RuleQuery query = new RuleQuery() @@ -480,7 +481,8 @@ public class RuleIndexIT { @Test public void search_by_security_owaspTop10_2021_return_vulnerabilities_and_hotspots_only() { - RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10-2021:a1", "owaspTop10-2021:a10", "cwe:543")), r -> r.setType(VULNERABILITY)); + RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10-2021:a1", "owaspTop10-2021:a10", "cwe:543")), + r -> r.setType(VULNERABILITY)); RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10-2021:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT)); createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL)); index(); @@ -521,7 +523,8 @@ public class RuleIndexIT { // Creation of one rule for each standard security category defined (except other) for (Map.Entry> sqCategorySetEntry : SecurityStandards.CWES_BY_SQ_CATEGORY.entrySet()) { - rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), r -> r.setType(SECURITY_HOTSPOT))); + rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), + r -> r.setType(SECURITY_HOTSPOT))); } index(); @@ -553,9 +556,11 @@ public class RuleIndexIT { db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2); index(); - verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(anotherProfile), anotherProfileRule1, anotherProfileRule2); + verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(anotherProfile), anotherProfileRule1, + anotherProfileRule2); verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(anotherProfile), commonRule); - verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(profile), commonRule, profileRule1, profileRule2, profileRule3); + verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(profile), commonRule, profileRule1, + profileRule2, profileRule3); verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(profile)); } @@ -704,7 +709,8 @@ public class RuleIndexIT { verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(INHERITED.name(), OVERRIDES.name()))); // inherited AND overridden on child - verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name(), OVERRIDES.name())), rule1, rule2, rule3); + verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name(), OVERRIDES.name())), rule1, + rule2, rule3); } @Test @@ -812,17 +818,19 @@ public class RuleIndexIT { index(); RuleQuery query = new RuleQuery(); - SearchIdResult result1 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), new SearchOptions()); + SearchIdResult result1 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), + new SearchOptions()); assertThat(result1.getUuids()).isEmpty(); query = new RuleQuery(); - SearchIdResult result2 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.SECURITY.name())), new SearchOptions()); + SearchIdResult result2 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.SECURITY.name())), + new SearchOptions()); assertThat(result2.getUuids()).containsOnly(phpRule.getUuid()); } @Test public void search_by_severity() { - ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH); + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.BLOCKER); RuleDto phpRule = createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); index(); @@ -831,7 +839,7 @@ public class RuleIndexIT { assertThat(result1.getUuids()).isEmpty(); query = new RuleQuery(); - SearchIdResult result2 = underTest.search(query.setImpactSeverities(List.of(Severity.HIGH.name())), new SearchOptions()); + SearchIdResult result2 = underTest.search(query.setImpactSeverities(List.of(Severity.BLOCKER.name())), new SearchOptions()); assertThat(result2.getUuids()).containsOnly(phpRule.getUuid()); } @@ -846,7 +854,8 @@ public class RuleIndexIT { SearchIdResult result2 = underTest.search(query, new SearchOptions().addFacets(singletonList("cleanCodeAttributeCategories"))); assertThat(result2.getFacets().getAll()).hasSize(1); - assertThat(result2.getFacets().getAll().get("cleanCodeAttributeCategories")).containsOnly(entry("ADAPTABLE", 1L), entry("INTENTIONAL", 1L)); + assertThat(result2.getFacets().getAll().get("cleanCodeAttributeCategories")).containsOnly(entry("ADAPTABLE", 1L), entry("INTENTIONAL" + , 1L)); } @Test @@ -859,7 +868,8 @@ public class RuleIndexIT { RuleQuery query = new RuleQuery(); SearchIdResult result = underTest.search( - query.setCleanCodeAttributesCategories(List.of(CleanCodeAttributeCategory.CONSISTENT.name(), CleanCodeAttributeCategory.ADAPTABLE.name())), + query.setCleanCodeAttributesCategories(List.of(CleanCodeAttributeCategory.CONSISTENT.name(), + CleanCodeAttributeCategory.ADAPTABLE.name())), new SearchOptions().addFacets(singletonList("cleanCodeAttributeCategories"))); assertThat(result.getUuids()).containsExactlyInAnyOrder(php.getUuid(), java.getUuid()); @@ -895,7 +905,8 @@ public class RuleIndexIT { RuleQuery query = new RuleQuery(); - SearchIdResult result2 = underTest.search(query.setImpactSeverities(Set.of(Severity.HIGH.name())), new SearchOptions().addFacets(singletonList("impactSoftwareQualities"))); + SearchIdResult result2 = underTest.search(query.setImpactSeverities(of(Severity.HIGH.name())), + new SearchOptions().addFacets(singletonList("impactSoftwareQualities"))); assertThat(result2.getFacets().getAll()).hasSize(1); assertThat(result2.getFacets().getAll().get("impactSoftwareQualities")) @@ -910,14 +921,21 @@ public class RuleIndexIT { ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH); ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW); ImpactDto impactDto3 = new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW); + ImpactDto impactDto4 = new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.BLOCKER); + ImpactDto impactDto5 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.BLOCKER); + ImpactDto impactDto6 = new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.INFO); createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); - createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2, impactDto3))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto3))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto4))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto5))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto6))); index(); RuleQuery query = new RuleQuery(); SearchIdResult result = underTest.search( - query.setImpactSeverities(Set.of(Severity.LOW.name())).setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), + query.setImpactSeverities(of(Severity.LOW.name())).setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), new SearchOptions().addFacets(List.of("impactSoftwareQualities", "impactSeverities"))); assertThat(result.getFacets().getAll()).hasSize(2); @@ -931,15 +949,18 @@ public class RuleIndexIT { .containsOnly( entry("HIGH", 1L), entry("MEDIUM", 0L), - entry("LOW", 1L)); + entry("LOW", 1L), + entry("INFO", 0L), + entry("BLOCKER", 1L)); } @Test public void search_should_support_severity_facet() { ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH); ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW); + ImpactDto impactDto3 = new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.BLOCKER); createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); - createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2, impactDto3))); index(); RuleQuery query = new RuleQuery(); @@ -947,40 +968,65 @@ public class RuleIndexIT { SearchIdResult result2 = underTest.search(query, new SearchOptions().addFacets(singletonList("impactSeverities"))); assertThat(result2.getFacets().getAll()).hasSize(1); - assertThat(result2.getFacets().getAll().get("impactSeverities")).containsOnly(entry("LOW", 1L), entry("MEDIUM", 0L), entry("HIGH", 1L)); + assertThat(result2.getFacets().getAll().get("impactSeverities")) + .containsOnly( + entry("LOW", 1L), + entry("MEDIUM", 0L), + entry("HIGH", 1L), + entry("INFO", 0L), + entry("BLOCKER", 1L)); } @Test public void search_should_support_severity_facet_with_filters() { ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH); ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW); + ImpactDto impactDto3 = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.INFO); + ImpactDto impactDto4 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.INFO); + ImpactDto impactDto5 = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.BLOCKER); createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); - createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2, impactDto3))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto4, impactDto5))); index(); RuleQuery query = new RuleQuery(); - SearchIdResult result2 = underTest.search(query.setImpactSeverities(Set.of("LOW")), new SearchOptions().addFacets(singletonList("impactSeverities"))); + SearchIdResult result2 = underTest.search(query.setImpactSeverities(of("LOW")), new SearchOptions().addFacets(singletonList( + "impactSeverities"))); assertThat(result2.getFacets().getAll()).hasSize(1); - assertThat(result2.getFacets().getAll().get("impactSeverities")).containsOnly(entry("LOW", 1L), entry("MEDIUM", 0L), entry("HIGH", 1L)); + assertThat(result2.getFacets().getAll().get("impactSeverities")) + .containsOnly( + entry("LOW", 1L), + entry("MEDIUM", 0L), + entry("HIGH", 1L), + entry("INFO", 2L), + entry("BLOCKER", 1L)); } @Test public void search_should_support_software_quality_and_severity_facets_with_filtering() { ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH); ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW); + ImpactDto impactDto3 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.BLOCKER); createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto3))); index(); - RuleQuery query = new RuleQuery().setImpactSeverities(Set.of("LOW")) - .setImpactSoftwareQualities(Set.of(SoftwareQuality.MAINTAINABILITY.name())); + RuleQuery query = new RuleQuery().setImpactSeverities(of("LOW")) + .setImpactSoftwareQualities(of(SoftwareQuality.MAINTAINABILITY.name())); SearchOptions searchOptions = new SearchOptions().addFacets(List.of("impactSeverities", "impactSoftwareQualities")); SearchIdResult result2 = underTest.search(query, searchOptions); assertThat(result2.getFacets().getAll()).hasSize(2); - assertThat(result2.getFacets().getAll().get("impactSeverities")).containsOnly(entry("LOW", 1L), entry("MEDIUM", 0L), entry("HIGH", 0L)); + assertThat(result2.getFacets().getAll().get("impactSeverities")) + .containsOnly( + entry("LOW", 1L), + entry("MEDIUM", 0L), + entry("HIGH", 0L), + entry("INFO", 0L), + entry("BLOCKER", 1L)); assertThat(result2.getFacets().getAll().get("impactSoftwareQualities")).containsOnly( entry(SoftwareQuality.SECURITY.name(), 0L), entry(SoftwareQuality.MAINTAINABILITY.name(), 1L), @@ -1000,7 +1046,8 @@ public class RuleIndexIT { assertThat(result1.getFacets().getAll()).isEmpty(); // should not have any facet on non matching query! - SearchIdResult result2 = underTest.search(new RuleQuery().setQueryText("aeiou"), new SearchOptions().addFacets(singletonList("repositories"))); + SearchIdResult result2 = underTest.search(new RuleQuery().setQueryText("aeiou"), new SearchOptions().addFacets(singletonList( + "repositories"))); assertThat(result2.getFacets().getAll()).hasSize(1); assertThat(result2.getFacets().getAll().get("repositories")).isEmpty(); @@ -1070,7 +1117,8 @@ public class RuleIndexIT { setupStickyFacets(); RuleQuery query = new RuleQuery().setLanguages(ImmutableList.of("cpp")); - SearchIdResult result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS))); + SearchIdResult result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, + FACET_TAGS))); assertThat(result.getUuids()).hasSize(3); assertThat(result.getFacets().getAll()).hasSize(3); assertThat(result.getFacets().get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java", "cobol"); @@ -1194,7 +1242,8 @@ public class RuleIndexIT { .setTags(ImmutableList.of("T2")) .setTypes(asList(BUG, CODE_SMELL)); - SearchIdResult result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS, + SearchIdResult result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, + FACET_TAGS, FACET_TYPES))); assertThat(result.getUuids()).hasSize(2); assertThat(result.getFacets().getAll()).hasSize(4); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java index 4bbc2b54ac7..247a1829f98 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/DebtRatingGrid.java @@ -78,17 +78,6 @@ public class DebtRatingGrid { .orElseThrow(() -> new IllegalArgumentException(format("Invalid value '%s'", value))); } - /** - * Computes a rating from A to D, where E is converted to D. - */ - public Rating getAToDRatingForDensity(double value) { - return ratingBounds.entrySet().stream() - .filter(e -> e.getValue().match(value)) - .map(e -> e.getKey() == E ? D : e.getKey()) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException(format("Invalid value '%s'", value))); - } - public double getGradeLowerBound(Rating rating) { if (rating.getIndex() > 1) { return gridValues[rating.getIndex() - 2]; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java index 3167b5d03ee..eae57b9d482 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java @@ -23,7 +23,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.CheckForNull; @@ -32,7 +31,8 @@ import org.sonar.api.issue.impact.Severity; import static org.sonar.api.utils.Preconditions.checkArgument; /** - * Builder class to help build measures based on impacts with payload such as @{link {@link org.sonar.api.measures.CoreMetrics#RELIABILITY_ISSUES}}. + * Builder class to help build measures based on impacts with payload such as @{link + * {@link org.sonar.api.measures.CoreMetrics#RELIABILITY_ISSUES}}. */ public class ImpactMeasureBuilder { @@ -65,9 +65,16 @@ public class ImpactMeasureBuilder { return new ImpactMeasureBuilder(map); } - private static void checkImpactMap(Map map) { - checkArgument(map.containsKey(TOTAL_KEY), "Map must contain a total key"); - Arrays.stream(Severity.values()).forEach(severity -> checkArgument(map.containsKey(severity.name()), "Map must contain a key for severity " + severity.name())); + /** + * As we moved from 3 to 5 severities, we need to be able to handle measures saved with missing severities. + */ + private static void checkImpactMap(Map impactMap) { + checkArgument(impactMap.containsKey(TOTAL_KEY), "Map must contain a total key"); + for (Severity severity : Severity.values()) { + if (!impactMap.containsKey(severity.name())) { + impactMap.put(severity.name(), 0L); + } + } } public static ImpactMeasureBuilder fromString(String value) { diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/Rating.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/Rating.java index f00b8796dd8..3ba14d771a3 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/Rating.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/Rating.java @@ -45,9 +45,11 @@ public enum Rating { INFO, A); public static final Map RATING_BY_SOFTWARE_QUALITY_SEVERITY = Map.of( + Severity.BLOCKER, Rating.E, Severity.HIGH, Rating.D, Severity.MEDIUM, Rating.C, - Severity.LOW, Rating.B); + Severity.LOW, Rating.B, + Severity.INFO, Rating.A); private final int index; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java index fecce77fcd4..2c2a1d017b1 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java @@ -26,7 +26,7 @@ import org.sonar.server.measure.Rating; /** * This class defines "default" measures values for the "Software Quality Rating Metrics" when they do not exist. - * The "default" value is the same as the equivalent "Rule Type Rating Metric", except for E Rating that is converted to D Rating + * The "default" value is the same as the equivalent "Rule Type Rating Metric" * If the "Software Quality Rating Metrics" exists, then no changes are made */ public class ProjectMeasuresSoftwareQualityRatingsInitializer { @@ -35,9 +35,7 @@ public class ProjectMeasuresSoftwareQualityRatingsInitializer { CoreMetrics.SQALE_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, CoreMetrics.RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, CoreMetrics.SECURITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, - CoreMetrics.SECURITY_REVIEW_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, CoreMetrics.NEW_SECURITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, - CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, CoreMetrics.NEW_RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY ); @@ -59,7 +57,7 @@ public class ProjectMeasuresSoftwareQualityRatingsInitializer { Double value = measures.get(ruleTypeMetric); if (value != null) { - measures.put(softwareQualityMetric, value > Rating.D.getIndex() ? Double.valueOf(Rating.D.getIndex()) : value); + measures.put(softwareQualityMetric, value); } } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java index 69791800a7b..1af9a5db8c8 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java @@ -56,14 +56,4 @@ public class SecurityReviewRating { return E; } - public static Rating computeAToDRating(@Nullable Double percent) { - if (percent == null || Math.abs(percent - 100.0D) < 10e-6) { - return A; - } else if (percent >= 70.0D) { - return B; - } else if (percent >= 50.0D) { - return C; - } - return D; - } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java index 6557e108815..8a312cba599 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/measure/DebtRatingGridTest.java @@ -54,21 +54,6 @@ public class DebtRatingGridTest { assertThat(ratingGrid.getRatingForDensity(1.01)).isEqualTo(E); } - @Test - public void getAToDRatingForDensity_returnsValueBetweenAAndD() { - assertThat(ratingGrid.getAToDRatingForDensity(0)).isEqualTo(A); - assertThat(ratingGrid.getAToDRatingForDensity(0.05)).isEqualTo(A); - assertThat(ratingGrid.getAToDRatingForDensity(0.09999999)).isEqualTo(A); - assertThat(ratingGrid.getAToDRatingForDensity(0.1)).isEqualTo(A); - assertThat(ratingGrid.getAToDRatingForDensity(0.15)).isEqualTo(B); - assertThat(ratingGrid.getAToDRatingForDensity(0.2)).isEqualTo(B); - assertThat(ratingGrid.getAToDRatingForDensity(0.25)).isEqualTo(C); - assertThat(ratingGrid.getAToDRatingForDensity(0.5)).isEqualTo(C); - assertThat(ratingGrid.getAToDRatingForDensity(0.65)).isEqualTo(D); - assertThat(ratingGrid.getAToDRatingForDensity(1)).isEqualTo(D); - assertThat(ratingGrid.getAToDRatingForDensity(1.01)).isEqualTo(D); - } - @Test public void density_matching_exact_grid_values() { assertThat(ratingGrid.getRatingForDensity(0.1)).isEqualTo(A); diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java index 82f26c979c7..a7a393a6c62 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java @@ -19,12 +19,17 @@ */ package org.sonar.server.measure; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; -import org.sonar.api.issue.impact.Severity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.issue.impact.Severity.BLOCKER; +import static org.sonar.api.issue.impact.Severity.HIGH; +import static org.sonar.api.issue.impact.Severity.INFO; +import static org.sonar.api.issue.impact.Severity.LOW; +import static org.sonar.api.issue.impact.Severity.MEDIUM; class ImpactMeasureBuilderTest { @@ -32,17 +37,17 @@ class ImpactMeasureBuilderTest { void createEmptyMeasure_shouldReturnMeasureWithAllFields() { ImpactMeasureBuilder builder = ImpactMeasureBuilder.createEmpty(); assertThat(builder.buildAsMap()) - .containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L)); - } - - private static Map getImpactMap(Long total, Long high, Long medium, Long low) { - return Map.of("total", total, Severity.HIGH.name(), high, Severity.MEDIUM.name(), medium, Severity.LOW.name(), low); + .containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L, 0L, 0L)); } @Test void fromMap_shouldInitializeCorrectlyTheBuilder() { Map map = getImpactMap(6L, 3L, 2L, 1L); ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(map); + + map.put(INFO.name(), 0L); + map.put(BLOCKER.name(), 0L); + assertThat(builder.buildAsMap()) .isEqualTo(map); } @@ -65,8 +70,9 @@ class ImpactMeasureBuilderTest { LOW: 1 } """); + Map expectedMap = getImpactMap(6L, 3L, 2L, 1L, 0L, 0L); assertThat(builder.buildAsMap()) - .isEqualTo(getImpactMap(6L, 3L, 2L, 1L)); + .isEqualTo(expectedMap); } @Test @@ -77,45 +83,31 @@ class ImpactMeasureBuilderTest { .hasMessage("Map must contain a total key"); } - @Test - void buildAsMap_whenMissingSeverity_shouldThrowException() { - ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() - .setTotal(1L) - .setSeverity(Severity.HIGH, 1L) - .setSeverity(Severity.MEDIUM, 1L); - assertThatThrownBy(impactMeasureBuilder::buildAsMap) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Map must contain a key for severity LOW"); - } - - @Test - void buildAsString_whenMissingSeverity_shouldThrowException() { - ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() - .setTotal(1L) - .setSeverity(Severity.HIGH, 1L) - .setSeverity(Severity.MEDIUM, 1L); - assertThatThrownBy(impactMeasureBuilder::buildAsString) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Map must contain a key for severity LOW"); - } - @Test void setSeverity_shouldInitializeSeverityValues() { ImpactMeasureBuilder builder = ImpactMeasureBuilder.newInstance() - .setSeverity(Severity.HIGH, 3L) - .setSeverity(Severity.MEDIUM, 2L) - .setSeverity(Severity.LOW, 1L) - .setTotal(6L); + .setSeverity(HIGH, 3L) + .setSeverity(MEDIUM, 2L) + .setSeverity(LOW, 1L) + .setSeverity(INFO, 4L) + .setSeverity(BLOCKER, 5L) + .setTotal(15L); assertThat(builder.buildAsMap()) - .isEqualTo(getImpactMap(6L, 3L, 2L, 1L)); + .isEqualTo(getImpactMap(15L, 3L, 2L, 1L, 4L, 5L)); } @Test void add_shouldSumImpactsAndTotal() { - ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L)) - .add(ImpactMeasureBuilder.newInstance().setTotal(6L).setSeverity(Severity.HIGH, 3L).setSeverity(Severity.MEDIUM, 2L).setSeverity(Severity.LOW, 1L)); + ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(11L, 3L, 2L, 1L, 1L, 4L)) + .add(ImpactMeasureBuilder.newInstance() + .setTotal(14L) + .setSeverity(HIGH, 3L) + .setSeverity(MEDIUM, 2L) + .setSeverity(LOW, 1L) + .setSeverity(INFO, 5L) + .setSeverity(BLOCKER, 3L)); assertThat(builder.buildAsMap()) - .isEqualTo(getImpactMap(12L, 6L, 4L, 2L)); + .isEqualTo(getImpactMap(25L, 6L, 4L, 2L, 6L, 7L)); } @Test @@ -128,10 +120,28 @@ class ImpactMeasureBuilderTest { } @Test - void getTotal_shoudReturnExpectedTotal() { + void getTotal_shouldReturnExpectedTotal() { ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L)); assertThat(builder.getTotal()).isEqualTo(6L); } + private static Map getImpactMap(Long total, Long high, Long medium, Long low) { + return new HashMap<>() { + { + put("total", total); + put(HIGH.name(), high); + put(MEDIUM.name(), medium); + put(LOW.name(), low); + } + }; + } + + private static Map getImpactMap(Long total, Long high, Long medium, Long low, Long info, Long blocker) { + Map impactMap = getImpactMap(total, high, medium, low); + impactMap.put(INFO.name(), info); + impactMap.put(BLOCKER.name(), blocker); + return impactMap; + } + } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java index ed9119174ec..e1dddaaafc0 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java @@ -49,10 +49,8 @@ class ProjectMeasuresSoftwareQualityRatingsInitializerTest { initialMeasures.put(CoreMetrics.SQALE_RATING_KEY, 1.0); initialMeasures.put(CoreMetrics.RELIABILITY_RATING_KEY, 2.0); initialMeasures.put(CoreMetrics.SECURITY_RATING_KEY, 3.0); - initialMeasures.put(CoreMetrics.SECURITY_REVIEW_RATING_KEY, 4.0); initialMeasures.put(CoreMetrics.NEW_SECURITY_RATING_KEY, 4.0); - initialMeasures.put(CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, 3.0); - initialMeasures.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, 2.0); + initialMeasures.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, 5.0); initialMeasures.put(CoreMetrics.NEW_RELIABILITY_RATING_KEY, 1.0); Map measures = new HashMap<>(initialMeasures); @@ -64,38 +62,8 @@ class ProjectMeasuresSoftwareQualityRatingsInitializerTest { .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 1.0) .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 2.0) .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, 3.0) - .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4.0) .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4.0) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 3.0) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 2.0) + .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 5.0) .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 1.0); } - - @Test - void initializeSoftwareQualityRatings_whenERating_thenSoftwareQualityRatingCreatedWithD() { - Map initialMeasures = new HashMap<>(); - initialMeasures.put(CoreMetrics.SQALE_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.RELIABILITY_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.SECURITY_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.SECURITY_REVIEW_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.NEW_SECURITY_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, (double) Rating.E.getIndex()); - initialMeasures.put(CoreMetrics.NEW_RELIABILITY_RATING_KEY, (double) Rating.E.getIndex()); - - Map measures = new HashMap<>(initialMeasures); - - ProjectMeasuresSoftwareQualityRatingsInitializer.initializeSoftwareQualityRatings(measures); - - assertThat(measures).hasSize(initialMeasures.size() * 2) - .containsAllEntriesOf(initialMeasures) - .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, (double) Rating.D.getIndex()) - .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, (double) Rating.D.getIndex()); - } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityReviewRatingTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityReviewRatingTest.java index 1d586900a1b..ec7c91b03b2 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityReviewRatingTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityReviewRatingTest.java @@ -33,7 +33,6 @@ import static org.sonar.server.measure.Rating.B; import static org.sonar.server.measure.Rating.C; import static org.sonar.server.measure.Rating.D; import static org.sonar.server.measure.Rating.E; -import static org.sonar.server.security.SecurityReviewRating.computeAToDRating; import static org.sonar.server.security.SecurityReviewRating.computePercent; import static org.sonar.server.security.SecurityReviewRating.computeRating; @@ -56,36 +55,12 @@ class SecurityReviewRatingTest { return res.toArray(new Object[res.size()][2]); } - private static Object[][] valuesForSoftwareQualityRatings() { - List res = new ArrayList<>(); - res.add(new Object[] {100.0, A}); - res.add(new Object[] {99.999999, A}); - res.add(new Object[] {99.99999, B}); - res.add(new Object[] {99.9, B}); - res.add(new Object[] {90.0, B}); - res.add(new Object[] {80.0, B}); - res.add(new Object[] {75.0, B}); - res.add(new Object[] {70.0, B}); - res.add(new Object[] {60, C}); - res.add(new Object[] {50.0, C}); - res.add(new Object[] {40.0, D}); - res.add(new Object[] {30.0, D}); - res.add(new Object[] {29.9, D}); - return res.toArray(new Object[res.size()][2]); - } - @ParameterizedTest @MethodSource("values") void compute_rating(double percent, Rating expectedRating) { assertThat(computeRating(percent)).isEqualTo(expectedRating); } - @ParameterizedTest - @MethodSource("valuesForSoftwareQualityRatings") - void compute_ratingForSoftwareQuality(double percent, Rating expectedRating) { - assertThat(computeAToDRating(percent)).isEqualTo(expectedRating); - } - @Test void compute_percent() { assertThat(computePercent(0, 0)).isEmpty(); diff --git a/server/sonar-web/config/jest/SetupReactTestingLibrary.ts b/server/sonar-web/config/jest/SetupReactTestingLibrary.ts index 6755fa753b9..d847ef36ad7 100644 --- a/server/sonar-web/config/jest/SetupReactTestingLibrary.ts +++ b/server/sonar-web/config/jest/SetupReactTestingLibrary.ts @@ -57,6 +57,39 @@ expect.extend({ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument(); }); + return result; + }, + async toHaveAPopoverWithContent(received: any, content: string) { + const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); + + if (!(received instanceof Element)) { + return { + pass: false, + message: () => `Received object is not an HTMLElement, and cannot have a tooltip`, + }; + } + + await user.click(received); + + const popover = await screen.findByRole('dialog'); + + const result = popover.textContent?.includes(content) + ? { + pass: true, + message: () => `Tooltip content "${popover.textContent}" contains expected "${content}"`, + } + : { + pass: false, + message: () => + `Tooltip content "${popover.textContent}" does not contain expected "${content}"`, + }; + + await user.keyboard('{Escape}'); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + return result; }, }); diff --git a/server/sonar-web/design-system/src/components/ColorsLegend.tsx b/server/sonar-web/design-system/src/components/ColorsLegend.tsx index 5974a136379..b1b111d0d47 100644 --- a/server/sonar-web/design-system/src/components/ColorsLegend.tsx +++ b/server/sonar-web/design-system/src/components/ColorsLegend.tsx @@ -64,12 +64,12 @@ export function ColorsLegend(props: ColorLegendProps) { ? { backgroundColor: color.backgroundColor ?? - themeColor(`bubble.legacy.${(idx + 1) as BubbleColorVal}`)({ + themeColor(`bubble.${(idx + 1) as BubbleColorVal}`)({ theme, }), borderColor: color.borderColor ?? - themeContrast(`bubble.legacy.${(idx + 1) as BubbleColorVal}`)({ + themeContrast(`bubble.${(idx + 1) as BubbleColorVal}`)({ theme, }), } diff --git a/server/sonar-web/design-system/src/components/FacetBox.tsx b/server/sonar-web/design-system/src/components/FacetBox.tsx index 792eb273123..f92d58c30a0 100644 --- a/server/sonar-web/design-system/src/components/FacetBox.tsx +++ b/server/sonar-web/design-system/src/components/FacetBox.tsx @@ -89,37 +89,38 @@ export function FacetBox(props: FacetBoxProps) { inner={inner} >
- { - if (!disabled) { - onClick?.(!open); - } - }} - > - {expandable && } - - {disabled ? ( - - - {name} - - - ) : ( - {name} - )} - + + { + if (!disabled) { + onClick?.(!open); + } + }} + > + {expandable && } + + {disabled ? ( + + + {name} + + + ) : ( + {name} + )} + {help && {help}} - + {} @@ -173,6 +174,11 @@ const BadgeAndIcons = styled.div` ${tw`sw-gap-2`}; `; +const TitleWithHelp = styled.div` + ${tw`sw-flex`}; + ${tw`sw-items-center`}; +`; + const ChevronAndTitle = styled(BareButton)<{ expandable?: boolean; }>` diff --git a/server/sonar-web/design-system/src/components/Pill.tsx b/server/sonar-web/design-system/src/components/Pill.tsx index acd24669bda..c2eb3fb56c5 100644 --- a/server/sonar-web/design-system/src/components/Pill.tsx +++ b/server/sonar-web/design-system/src/components/Pill.tsx @@ -24,20 +24,31 @@ import tw from 'twin.macro'; import { themeColor, themeContrast } from '../helpers/theme'; import { ThemeColors } from '../types/theme'; -type PillVariant = 'danger' | 'warning' | 'info' | 'accent'; +export enum PillVariant { + Critical = 'critical', + Danger = 'danger', + Warning = 'warning', + Caution = 'caution', + Info = 'info', + Accent = 'accent', +} const variantThemeColors: Record = { - danger: 'pillDanger', - warning: 'pillWarning', - info: 'pillInfo', - accent: 'pillAccent', + [PillVariant.Critical]: 'pillCritical', + [PillVariant.Danger]: 'pillDanger', + [PillVariant.Warning]: 'pillWarning', + [PillVariant.Caution]: 'pillCaution', + [PillVariant.Info]: 'pillInfo', + [PillVariant.Accent]: 'pillAccent', }; const variantThemeBorderColors: Record = { - danger: 'pillDangerBorder', - warning: 'pillWarningBorder', - info: 'pillInfoBorder', - accent: 'pillAccentBorder', + [PillVariant.Critical]: 'pillCriticalBorder', + [PillVariant.Danger]: 'pillDangerBorder', + [PillVariant.Warning]: 'pillWarningBorder', + [PillVariant.Caution]: 'pillCautionBorder', + [PillVariant.Info]: 'pillInfoBorder', + [PillVariant.Accent]: 'pillAccentBorder', }; interface PillProps { @@ -84,7 +95,7 @@ const StyledPill = styled.span<{ background-color: ${({ variant }) => themeColor(variantThemeColors[variant])}; color: ${({ variant }) => themeContrast(variantThemeColors[variant])}; - border-style: ${({ variant }) => (variant === 'accent' ? 'hidden' : 'solid')}; + border-style: ${({ variant }) => (variant === PillVariant.Accent ? 'hidden' : 'solid')}; border-color: ${({ variant }) => themeColor(variantThemeBorderColors[variant])}; `; @@ -95,7 +106,7 @@ const StyledPillButton = styled.button<{ background-color: ${({ variant }) => themeColor(variantThemeColors[variant])}; color: ${({ variant }) => themeContrast(variantThemeColors[variant])}; - border-style: ${({ variant }) => (variant === 'accent' ? 'hidden' : 'solid')}; + border-style: ${({ variant }) => (variant === PillVariant.Accent ? 'hidden' : 'solid')}; border-color: ${({ variant }) => themeColor(variantThemeBorderColors[variant])}; cursor: pointer; diff --git a/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx index 5c385ce0cec..014f020809e 100644 --- a/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx @@ -19,9 +19,9 @@ */ import { screen } from '@testing-library/react'; import { render } from '../../helpers/testUtils'; -import { Pill } from '../Pill'; +import { Pill, PillVariant } from '../Pill'; it('should render correctly', () => { - render(23); + render(23); expect(screen.getByText('23')).toBeInTheDocument(); }); diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap index 900f1f226d9..741beaababd 100644 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/HotspotRating-test.tsx.snap @@ -74,7 +74,7 @@ exports[`should render HotspotRating with MEDIUM rating 1`] = ` + + + ); +} diff --git a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityHighIcon.tsx b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityHighIcon.tsx index 52b7aa4e977..dd5cd03ed2e 100644 --- a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityHighIcon.tsx +++ b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityHighIcon.tsx @@ -29,10 +29,10 @@ export function SoftwareImpactSeverityHighIcon({ const color = disabled ? 'iconSoftwareImpactSeverityDisabled' : 'iconSoftwareImpactSeverityHigh'; return ( - + diff --git a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityInfoIcon.tsx b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityInfoIcon.tsx new file mode 100644 index 00000000000..7939c630b8d --- /dev/null +++ b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityInfoIcon.tsx @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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. + */ +import styled from '@emotion/styled'; +import { IconInfo } from '@sonarsource/echoes-react'; +import { IconProps } from './Icon'; + +const defaultIconSize = 15; + +export function SoftwareImpactSeverityInfoIcon({ + disabled, + ...iconProps +}: IconProps & { disabled?: boolean }) { + const color = disabled ? 'echoes-color-icon-disabled' : 'echoes-color-icon-info'; + + return ; +} + +// Info icon is the only one that is imported from echoes, so we need to adjust its size +const StyledIconInfo = styled(IconInfo)` + ${(props: IconProps & { disabled?: boolean }) => { + let size = props.width ?? props.height; + size = size ? size + 1 : defaultIconSize; + + return ` + font-size: ${size}px; + margin-left: -0.5px; + `; + }}; +`; diff --git a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityLowIcon.tsx b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityLowIcon.tsx index 305cee4beda..ef943283fed 100644 --- a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityLowIcon.tsx +++ b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityLowIcon.tsx @@ -29,10 +29,10 @@ export function SoftwareImpactSeverityLowIcon({ const color = disabled ? 'iconSoftwareImpactSeverityDisabled' : 'iconSoftwareImpactSeverityLow'; return ( - + diff --git a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityMediumIcon.tsx b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityMediumIcon.tsx index 5d54e0f6e91..417ff73c76c 100644 --- a/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityMediumIcon.tsx +++ b/server/sonar-web/design-system/src/components/icons/SoftwareImpactSeverityMediumIcon.tsx @@ -31,10 +31,10 @@ export function SoftwareImpactSeverityMediumIcon({ : 'iconSoftwareImpactSeverityMedium'; return ( - + diff --git a/server/sonar-web/design-system/src/components/icons/index.ts b/server/sonar-web/design-system/src/components/icons/index.ts index 1cccc42ba0c..45d0a0c4ff9 100644 --- a/server/sonar-web/design-system/src/components/icons/index.ts +++ b/server/sonar-web/design-system/src/components/icons/index.ts @@ -78,7 +78,9 @@ export { SeverityInfoIcon } from './SeverityInfoIcon'; export { SeverityMajorIcon } from './SeverityMajorIcon'; export { SeverityMinorIcon } from './SeverityMinorIcon'; export { SnoozeCircleIcon } from './SnoozeCircleIcon'; +export { SoftwareImpactSeverityBlockerIcon } from './SoftwareImpactSeverityBlockerIcon'; export { SoftwareImpactSeverityHighIcon } from './SoftwareImpactSeverityHighIcon'; +export { SoftwareImpactSeverityInfoIcon } from './SoftwareImpactSeverityInfoIcon'; export { SoftwareImpactSeverityLowIcon } from './SoftwareImpactSeverityLowIcon'; export { SoftwareImpactSeverityMediumIcon } from './SoftwareImpactSeverityMediumIcon'; export { SortAscendIcon } from './SortAscendIcon'; diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 46771d97fd7..da0bc81f8ef 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -76,7 +76,7 @@ export * from './MultiSelector'; export * from './NavBarTabs'; export * from './NewCodeLegend'; export * from './OutsideClickHandler'; -export { Pill } from './Pill'; +export * from './Pill'; export * from './popups'; export { QualityGateIndicator } from './QualityGateIndicator'; export * from './SearchHighlighter'; diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx index 9276c9b286a..2ca4a739a7f 100644 --- a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx +++ b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx @@ -26,7 +26,6 @@ import { RatingLabel } from '../types/measures'; type sizeType = keyof typeof SIZE_MAPPING; interface Props extends React.AriaAttributes { className?: string; - isLegacy?: boolean; label?: string; rating?: RatingLabel; size?: sizeType; @@ -41,10 +40,7 @@ const SIZE_MAPPING = { }; export const MetricsRatingBadge = forwardRef( - ( - { className, size = 'sm', isLegacy = true, label, rating, ...ariaAttrs }: Readonly, - ref, - ) => { + ({ className, size = 'sm', label, rating, ...ariaAttrs }: Readonly, ref) => { if (!rating) { return ( ( { }; const MetricsRatingBadgeStyled = styled.div<{ - isLegacy: boolean; rating: RatingLabel; size: string; }>` @@ -105,8 +99,7 @@ const MetricsRatingBadgeStyled = styled.div<{ height: ${getProp('size')}; color: ${({ rating }) => themeContrast(`rating.${rating}`)}; font-size: ${({ size }) => getFontSize(size)}; - background-color: ${({ rating, isLegacy }) => - themeColor(`rating.${isLegacy ? 'legacy.' : ''}${rating}`)}; + background-color: ${({ rating }) => themeColor(`rating.${rating}`)}; user-select: none; display: inline-flex; diff --git a/server/sonar-web/design-system/src/theme/light.ts b/server/sonar-web/design-system/src/theme/light.ts index ef98fce9ac9..77301732b64 100644 --- a/server/sonar-web/design-system/src/theme/light.ts +++ b/server/sonar-web/design-system/src/theme/light.ts @@ -341,10 +341,14 @@ export const lightTheme = { badgeCounterFailedBorder: COLORS.red[200], // pills + pillCritical: COLORS.red[100], + pillCriticalBorder: COLORS.red[800], pillDanger: COLORS.red[50], - pillDangerBorder: COLORS.red[300], - pillWarning: COLORS.yellow[50], - pillWarningBorder: COLORS.yellow[300], + pillDangerBorder: COLORS.red[600], + pillWarning: COLORS.orange[50], + pillWarningBorder: COLORS.orange[300], + pillCaution: COLORS.yellow[50], + pillCautionBorder: COLORS.yellow[300], pillInfo: COLORS.blue[50], pillInfoBorder: COLORS.blue[300], pillAccent: COLORS.indigo[50], @@ -401,9 +405,11 @@ export const lightTheme = { destructiveIconFocus: danger.default, // icons - iconSoftwareImpactSeverityHigh: COLORS.red[500], - iconSoftwareImpactSeverityMedium: COLORS.yellow[700], - iconSoftwareImpactSeverityLow: COLORS.blue[700], + iconSoftwareImpactSeverityBlocker: COLORS.red[800], + iconSoftwareImpactSeverityHigh: COLORS.red[600], + iconSoftwareImpactSeverityMedium: COLORS.orange[400], + iconSoftwareImpactSeverityLow: COLORS.yellow[500], + iconSoftwareImpactSeverityInfo: COLORS.blue[600], iconSoftwareImpactSeverityDisabled: COLORS.blueGrey[300], iconSeverityMajor: danger.light, iconSeverityMinor: COLORS.yellowGreen[400], @@ -466,18 +472,11 @@ export const lightTheme = { // size indicators sizeIndicator: COLORS.blue[500], - // rating colors - 'rating.legacy.A': COLORS.green[200], - 'rating.legacy.B': COLORS.yellowGreen[200], - 'rating.legacy.C': COLORS.yellow[200], - 'rating.legacy.D': COLORS.orange[200], - 'rating.legacy.E': COLORS.red[200], - // rating colors 'rating.A': COLORS.green[200], 'rating.B': COLORS.yellowGreen[200], 'rating.C': COLORS.yellow[200], - 'rating.D': COLORS.red[200], + 'rating.D': COLORS.orange[200], 'rating.E': COLORS.red[200], 'portfolio.rating.A.text': COLORS.green[900], @@ -489,9 +488,9 @@ export const lightTheme = { 'portfolio.rating.C.text': COLORS.yellow[900], 'portfolio.rating.C.background': COLORS.yellow[100], 'portfolio.rating.C.border': COLORS.yellow[500], - 'portfolio.rating.D.text': COLORS.red[900], - 'portfolio.rating.D.background': COLORS.red[100], - 'portfolio.rating.D.border': COLORS.red[400], + 'portfolio.rating.D.text': COLORS.orange[900], + 'portfolio.rating.D.background': COLORS.orange[100], + 'portfolio.rating.D.border': COLORS.orange[300], 'portfolio.rating.E.text': COLORS.red[900], 'portfolio.rating.E.background': COLORS.red[100], 'portfolio.rating.E.border': COLORS.red[400], @@ -499,25 +498,6 @@ export const lightTheme = { 'portfolio.rating.NONE.background': COLORS.blueGrey[50], 'portfolio.rating.NONE.border': COLORS.blueGrey[200], - 'portfolio.rating.legacy.A.text': COLORS.green[900], - 'portfolio.rating.legacy.A.background': COLORS.green[100], - 'portfolio.rating.legacy.A.border': COLORS.green[400], - 'portfolio.rating.legacy.B.text': COLORS.yellowGreen[900], - 'portfolio.rating.legacy.B.background': COLORS.yellowGreen[100], - 'portfolio.rating.legacy.B.border': COLORS.yellowGreen[400], - 'portfolio.rating.legacy.C.text': COLORS.yellow[900], - 'portfolio.rating.legacy.C.background': COLORS.yellow[100], - 'portfolio.rating.legacy.C.border': COLORS.yellow[500], - 'portfolio.rating.legacy.D.text': COLORS.orange[900], - 'portfolio.rating.legacy.D.background': COLORS.orange[100], - 'portfolio.rating.legacy.D.border': COLORS.orange[300], - 'portfolio.rating.legacy.E.text': COLORS.red[900], - 'portfolio.rating.legacy.E.background': COLORS.red[100], - 'portfolio.rating.legacy.E.border': COLORS.red[400], - 'portfolio.rating.legacy.NONE.text': COLORS.blueGrey[300], - 'portfolio.rating.legacy.NONE.background': COLORS.blueGrey[50], - 'portfolio.rating.legacy.NONE.border': COLORS.blueGrey[200], - // rating donut outside circle indicators 'ratingDonut.A': COLORS.green[400], 'ratingDonut.B': COLORS.yellowGreen[400], @@ -676,29 +656,18 @@ export const lightTheme = { // bubble charts bubbleChartLine: COLORS.grey[50], bubbleDefault: [...COLORS.blue[500], 0.3], - 'bubble.legacy.1': [...COLORS.green[500], 0.3], - 'bubble.legacy.2': [...COLORS.yellowGreen[500], 0.3], - 'bubble.legacy.3': [...COLORS.yellow[500], 0.3], - 'bubble.legacy.4': [...COLORS.orange[500], 0.3], - 'bubble.legacy.5': [...COLORS.red[500], 0.3], 'bubble.1': [...COLORS.green[500], 0.3], 'bubble.2': [...COLORS.yellowGreen[500], 0.3], 'bubble.3': [...COLORS.yellow[500], 0.3], - 'bubble.4': [...COLORS.red[500], 0.3], + 'bubble.4': [...COLORS.orange[500], 0.3], 'bubble.5': [...COLORS.red[500], 0.3], // TreeMap Colors - 'treeMap.legacy.A': COLORS.green[500], - 'treeMap.legacy.B': COLORS.yellowGreen[500], - 'treeMap.legacy.C': COLORS.yellow[500], - 'treeMap.legacy.D': COLORS.orange[500], - 'treeMap.legacy.E': COLORS.red[500], - 'treeMap.A': COLORS.green[500], 'treeMap.B': COLORS.yellowGreen[500], 'treeMap.C': COLORS.yellow[500], - 'treeMap.D': COLORS.red[500], + 'treeMap.D': COLORS.orange[500], 'treeMap.E': COLORS.red[500], 'treeMap.NA1': COLORS.blueGrey[300], @@ -843,12 +812,11 @@ export const lightTheme = { badgeCounterFailed: danger.dark, // pills - pillDanger: COLORS.red[800], - pillDangerIcon: COLORS.red[700], - pillWarning: COLORS.yellow[800], - pillWarningIcon: COLORS.yellow[700], + pillCritical: COLORS.red[800], + pillDanger: COLORS.red[700], + pillWarning: COLORS.orange[800], + pillCaution: COLORS.yellow[800], pillInfo: COLORS.blue[800], - pillInfoIcon: COLORS.blue[700], pillAccent: COLORS.indigo[500], // project cards @@ -945,12 +913,6 @@ export const lightTheme = { // page pageBlock: secondary.darker, - // overview software impact breakdown - overviewSoftwareImpactSeverityNeutral: COLORS.blueGrey[500], - overviewSoftwareImpactSeverityHigh: COLORS.red[700], - overviewSoftwareImpactSeverityMedium: COLORS.yellow[800], - overviewSoftwareImpactSeverityLow: COLORS.blue[800], - // graph - chart graphZoomHandleColor: COLORS.white, @@ -967,16 +929,11 @@ export const lightTheme = { // bubble charts bubbleDefault: COLORS.blue[500], - 'bubble.legacy.1': COLORS.green[500], - 'bubble.legacy.2': COLORS.yellowGreen[500], - 'bubble.legacy.3': COLORS.yellow[500], - 'bubble.legacy.4': COLORS.orange[500], - 'bubble.legacy.5': COLORS.red[500], 'bubble.1': COLORS.green[500], 'bubble.2': COLORS.yellowGreen[500], 'bubble.3': COLORS.yellow[500], - 'bubble.4': COLORS.red[500], + 'bubble.4': COLORS.orange[500], 'bubble.5': COLORS.red[500], // news bar diff --git a/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx b/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx index bdf31ea2a25..af82e478ded 100644 --- a/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx +++ b/server/sonar-web/src/main/js/app/components/ChangeInCalculationPill.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { Popover } from '@sonarsource/echoes-react'; -import { Pill } from 'design-system'; +import { Pill, PillVariant } from 'design-system'; import * as React from 'react'; import DocumentationLink from '../../components/common/DocumentationLink'; import { DocLink } from '../../helpers/doc-links'; @@ -49,7 +49,11 @@ export default function ChangeInCalculation({ qualifier }: Readonly) { } > - setIsPopoverOpen(!isPopoverOpen)}> + setIsPopoverOpen(!isPopoverOpen)} + > {translate('projects.awaiting_scan')} diff --git a/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx b/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx index ce59c6e03ce..f63301ef1f3 100644 --- a/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx +++ b/server/sonar-web/src/main/js/app/components/metrics/RatingComponent.tsx @@ -61,6 +61,8 @@ function isNewRatingMetric(metricKey: MetricKey) { const useGetMetricKeyForRating = (ratingMetric: RatingMetricKeys): MetricKey | null => { const { data: isLegacy, isLoading } = useIsLegacyCCTMode(); + const hasSoftwareQualityRating = !!SOFTWARE_QUALITY_RATING_METRICS_MAP[ratingMetric]; + if (isNewRatingMetric(ratingMetric)) { return ratingMetric; } @@ -68,7 +70,9 @@ const useGetMetricKeyForRating = (ratingMetric: RatingMetricKeys): MetricKey | n if (isLoading) { return null; } - return isLegacy ? ratingMetric : SOFTWARE_QUALITY_RATING_METRICS_MAP[ratingMetric]; + return isLegacy || !hasSoftwareQualityRating + ? ratingMetric + : SOFTWARE_QUALITY_RATING_METRICS_MAP[ratingMetric]; }; export default function RatingComponent(props: Readonly) { @@ -108,7 +112,6 @@ export default function RatingComponent(props: Readonly) { const badge = ( ) { const showComponentList = sourceViewer === undefined && components.length > 0 && !showSearch; + const { data: isLegacy, isLoading: isLoadingLegacy } = useIsLegacyCCTMode(); + const metricKeys = intersection( getCodeMetrics(component.qualifier, branchLike, { newCode: newCodeSelected }), Object.keys(metrics), @@ -121,10 +124,10 @@ export default function CodeAppRenderer(props: Readonly) { ); const filteredMetrics = difference(metricKeys, [ - ...(allComponentsHaveSoftwareQualityMeasures + ...(allComponentsHaveSoftwareQualityMeasures && !isLegacy ? OLD_TAXONOMY_METRICS : CCT_SOFTWARE_QUALITY_METRICS), - ...(allComponentsHaveRatings + ...(allComponentsHaveRatings && !isLegacy ? [...OLD_TAXONOMY_RATINGS, ...LEAK_OLD_TAXONOMY_RATINGS] : SOFTWARE_QUALITY_RATING_METRICS), ]).map((key) => metrics[key]); @@ -156,7 +159,7 @@ export default function CodeAppRenderer(props: Readonly) { )} - + {!allComponentsHaveSoftwareQualityMeasures && ( { // Filter by severity await user.click(ui.severetiesFacet.get()); - await user.click(ui.facetItem(/severity.HIGH/).get()); + await user.click(ui.facetItem(/severity_impact.HIGH/).get()); expect(ui.getAllRuleListItems()).toHaveLength(8); }); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts index 208cc1a0b98..cc8644c433a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts @@ -86,9 +86,11 @@ describe('custom rule', () => { await user.click(ui.cleanCodeQualityCheckbox(SoftwareQuality.Reliability).get()); await user.click(ui.cleanCodeSeveritySelect(SoftwareQuality.Reliability).get()); - await user.click(byRole('option', { name: 'severity.MEDIUM severity.MEDIUM' }).get()); + await user.click( + byRole('option', { name: 'severity_impact.MEDIUM severity_impact.MEDIUM' }).get(), + ); - expect(ui.createCustomRuleDialog.byText('severity.MEDIUM').get()).toBeInTheDocument(); + expect(ui.createCustomRuleDialog.byText('severity_impact.MEDIUM').get()).toBeInTheDocument(); await user.click(ui.statusSelect.get()); await user.click(byRole('option', { name: 'rules.status.BETA' }).get()); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx index c314a1150cf..76f99ceeb88 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { CLEAN_CODE_CATEGORIES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; export default function AttributeCategoryFacet(props: BasicProps) { const renderName = React.useCallback( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormFieldsCCT.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormFieldsCCT.tsx index 94e0d1f4447..5482b9a4147 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormFieldsCCT.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormFieldsCCT.tsx @@ -133,7 +133,7 @@ export function SoftwareQualitiesFields( () => IMPACT_SEVERITIES.map((severity) => ({ value: severity, - label: intl.formatMessage({ id: `severity.${severity}` }), + label: intl.formatMessage({ id: `severity_impact.${severity}` }), Icon: , })), [intl], diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx deleted file mode 100644 index 2f594df563f..00000000000 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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. - */ -import classNames from 'classnames'; -import { FacetBox, FacetItem } from 'design-system'; -import { orderBy, sortBy, without } from 'lodash'; -import * as React from 'react'; -import { formatMeasure } from '~sonar-aligned/helpers/measures'; -import { MetricType } from '~sonar-aligned/types/metrics'; -import Tooltip from '../../../components/controls/Tooltip'; -import { translate } from '../../../helpers/l10n'; -import { Dict } from '../../../types/types'; -import { FacetItemsList } from '../../issues/sidebar/FacetItemsList'; -import { MultipleSelectionHint } from '../../issues/sidebar/MultipleSelectionHint'; -import { FacetKey } from '../query'; - -export interface BasicProps { - help?: React.ReactNode; - onChange: (changes: Dict) => void; - onToggle: (facet: FacetKey) => void; - open: boolean; - stats?: Dict; - values: string[]; -} - -interface Props extends BasicProps { - disabled?: boolean; - disabledHelper?: string; - options?: string[]; - property: FacetKey; - renderFooter?: () => React.ReactNode; - renderName?: (value: string, disabled: boolean) => React.ReactNode; - renderTextName?: (value: string) => string; - singleSelection?: boolean; -} - -export default class Facet extends React.PureComponent { - handleItemClick = (itemValue: string, multiple: boolean) => { - const { values } = this.props; - let newValue; - if (this.props.singleSelection) { - const value = values.length ? values[0] : undefined; - newValue = itemValue === value ? undefined : itemValue; - } else if (multiple) { - newValue = orderBy( - values.includes(itemValue) ? without(values, itemValue) : [...values, itemValue], - ); - } else { - newValue = values.includes(itemValue) && values.length < 2 ? [] : [itemValue]; - } - this.props.onChange({ [this.props.property]: newValue }); - }; - - handleHeaderClick = () => this.props.onToggle(this.props.property); - - handleClear = () => this.props.onChange({ [this.props.property]: [] }); - - getStat = (value: string) => this.props.stats && this.props.stats[value]; - - renderItem = (value: string) => { - const active = this.props.values.includes(value); - const stat = this.getStat(value); - const disabled = stat === 0 || typeof stat === 'undefined'; - const { renderName = defaultRenderName, renderTextName = defaultRenderName } = this.props; - - return ( - - ); - }; - - render() { - const { - disabled, - disabledHelper, - open, - property, - renderTextName = defaultRenderName, - stats, - help, - values, - } = this.props; - const items = - this.props.options || - (stats && - sortBy( - Object.keys(stats), - (key) => -stats[key], - (key) => renderTextName(key).toLowerCase(), - )); - const headerId = `facet_${property}`; - const nbSelectableItems = - items?.filter((item) => (stats ? stats[item] : undefined)).length ?? 0; - const nbSelectedItems = values.length; - - return ( - - {open && items !== undefined && ( - {items.map(this.renderItem)} - )} - - {open && this.props.renderFooter !== undefined && this.props.renderFooter()} - - - - ); - } -} - -function defaultRenderName(value: string) { - return value; -} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx index e937feb4806..a1dbda02d91 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx @@ -21,6 +21,7 @@ import { BasicSeparator } from 'design-system'; import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; +import SeverityFacet from '../../../components/facets/SeverityFacet'; import { translate } from '../../../helpers/l10n'; import { Feature } from '../../../types/features'; import { Dict } from '../../../types/types'; @@ -33,7 +34,6 @@ import InheritanceFacet from './InheritanceFacet'; import PrioritizedRulesFacet from './PrioritizedRulesFacet'; import ProfileFacet from './ProfileFacet'; import RepositoryFacet from './RepositoryFacet'; -import SeverityFacet from './SeverityFacet'; import SoftwareQualityFacet from './SoftwareQualityFacet'; import StatusFacet from './StatusFacet'; import TagFacet from './TagFacet'; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/InheritanceFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/InheritanceFacet.tsx index 49a5fb2eae4..92080f9afef 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/InheritanceFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/InheritanceFacet.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { translate } from '../../../helpers/l10n'; import { RuleInheritance } from '../../../types/types'; -import Facet, { BasicProps } from './Facet'; interface Props extends Omit { disabled: boolean; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/PrioritizedRulesFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/PrioritizedRulesFacet.tsx index 66f1cc3b697..f739939fd89 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/PrioritizedRulesFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/PrioritizedRulesFacet.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; interface Props extends Omit { disabled: boolean; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RepositoryFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RepositoryFacet.tsx index f53ba7c4ae4..997f2f4bed1 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RepositoryFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RepositoryFacet.tsx @@ -21,12 +21,12 @@ import { Note } from 'design-system'; import * as React from 'react'; import { getRuleRepositories } from '../../../api/rules'; import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; +import { BasicProps } from '../../../components/facets/Facet'; import { translate } from '../../../helpers/l10n'; import { highlightTerm } from '../../../helpers/search'; import { Languages } from '../../../types/languages'; import { Dict } from '../../../types/types'; import { ListStyleFacet } from '../../issues/sidebar/ListStyleFacet'; -import { BasicProps } from './Facet'; interface StateProps { languages: Languages; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx deleted file mode 100644 index 6acfbcc3871..00000000000 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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. - */ - -import { HelperHintIcon } from 'design-system'; -import * as React from 'react'; -import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; -import SoftwareImpactSeverityIcon from '../../../components/icon-mappers/SoftwareImpactSeverityIcon'; -import { IMPACT_SEVERITIES } from '../../../helpers/constants'; -import { DocLink } from '../../../helpers/doc-links'; -import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; - -export default function SeverityFacet(props: BasicProps) { - const renderName = React.useCallback( - (severity: string, disabled: boolean) => ( -
- - {translate('severity', severity)} -
- ), - [], - ); - - const renderTextName = React.useCallback( - (severity: string) => translate('severity', severity), - [], - ); - - return ( - -

{translate('issues.facet.impactSeverities.help.line1')}

-

{translate('issues.facet.impactSeverities.help.line2')}

- - } - links={[ - { - href: DocLink.CleanCodeIntroduction, - label: translate('learn_more'), - }, - ]} - > - - - } - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx index 1097682a5aa..26151da5286 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { SOFTWARE_QUALITIES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; export default function SoftwareQualityFacet(props: BasicProps) { const renderName = React.useCallback( diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/StatusFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/StatusFacet.tsx index 8fcd7092240..e7d03dc66d2 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/StatusFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/StatusFacet.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { RULE_STATUSES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; export default class StatusFacet extends React.PureComponent { renderName = (status: string) => translate('rules.status', status.toLowerCase()); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/TagFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/TagFacet.tsx index 78c3fd4a660..ae3b0b11a13 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/TagFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/TagFacet.tsx @@ -20,10 +20,10 @@ import { uniq } from 'lodash'; import * as React from 'react'; import { getRuleTags } from '../../../api/rules'; +import { BasicProps } from '../../../components/facets/Facet'; import { translate } from '../../../helpers/l10n'; import { highlightTerm } from '../../../helpers/search'; import { ListStyleFacet } from '../../issues/sidebar/ListStyleFacet'; -import { BasicProps } from './Facet'; export default class TagFacet extends React.PureComponent { handleSearch = (query: string) => { diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx index 87fdc3d87cf..3d49c68bcb9 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx @@ -20,8 +20,8 @@ import { HelperHintIcon } from 'design-system'; import * as React from 'react'; import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; interface Props extends Omit { onChange: (changes: { template: boolean | undefined }) => void; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/TypeFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/TypeFacet.tsx index c92a3c11697..09ba4b0635d 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/TypeFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/TypeFacet.tsx @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Facet, { BasicProps } from '../../../components/facets/Facet'; import IssueTypeIcon from '../../../components/icon-mappers/IssueTypeIcon'; import { RULE_TYPES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; export default class TypeFacet extends React.PureComponent { renderName = (type: string) => ( diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 95165e1448a..332801e1d32 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -36,7 +36,6 @@ import { isDefined } from '../../../helpers/types'; import { getProjectUrl } from '../../../helpers/urls'; import { useCurrentBranchQuery } from '../../../queries/branch'; import { useComponentTreeQuery, useMeasuresComponentQuery } from '../../../queries/measures'; -import { useIsLegacyCCTMode } from '../../../queries/settings'; import { useLocation, useRouter } from '../../../sonar-aligned/components/hoc/withRouter'; import { BranchLike } from '../../../types/branch-like'; import { isApplication, isFile, isView } from '../../../types/component'; @@ -71,7 +70,6 @@ export default function MeasureContent(props: Readonly) { const { data: branchLike } = useCurrentBranchQuery(rootComponent); const router = useRouter(); const query = parseQuery(rawQuery); - const { data: isLegacy } = useIsLegacyCCTMode(); const { selected, asc, view } = query; const containerRef = React.useRef(null); @@ -209,14 +207,7 @@ export default function MeasureContent(props: Readonly) { ); } - return ( - - ); + return ; }; return ( diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx index 86cdf3e2be9..67f507873ab 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx @@ -32,7 +32,6 @@ import * as React from 'react'; import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; import { MetricKey } from '~sonar-aligned/types/metrics'; -import { SOFTWARE_QUALITY_RATING_METRICS } from '../../../helpers/constants'; import { getLocalizedMetricDomain, getLocalizedMetricName, @@ -42,7 +41,6 @@ import { import { getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures'; import { isDefined } from '../../../helpers/types'; import { getComponentDrilldownUrl } from '../../../helpers/urls'; -import { useIsLegacyCCTMode } from '../../../queries/settings'; import { BranchLike } from '../../../types/branch-like'; import { isProject, isView } from '../../../types/component'; import { @@ -88,7 +86,6 @@ export default function BubbleChartView(props: Readonly) { bubblesByDomain, } = props; const theme = useTheme(); - const { data: isLegacy } = useIsLegacyCCTMode(); const bubbleMetrics = getBubbleMetrics(bubblesByDomain, domain, metrics); const [ratingFilters, setRatingFilters] = React.useState<{ [rating: number]: boolean }>({}); @@ -116,8 +113,7 @@ export default function BubbleChartView(props: Readonly) { return undefined; } - const bubbleColor = - `bubble.${isLegacy ? 'legacy.' : ''}${(colorRating ?? 1) as BubbleColorVal}` as const; + const bubbleColor = `bubble.${(colorRating ?? 1) as BubbleColorVal}` as const; return { x, @@ -220,12 +216,6 @@ export default function BubbleChartView(props: Readonly) { {bubbleMetrics.colors && ( !SOFTWARE_QUALITY_RATING_METRICS.includes(m.key as MetricKey), - ) - } className="sw-mt-2" filters={ratingFilters} onRatingClick={handleRatingFilterClick} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx index 80acadf8b66..b765f2e4a25 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx @@ -33,14 +33,13 @@ import { translateWithParameters } from '../../../helpers/l10n'; export interface ColorRatingsLegendProps { className?: string; filters: { [rating: number]: boolean }; - isLegacy?: boolean; onRatingClick: (selection: number) => void; } export default function ColorRatingsLegend(props: ColorRatingsLegendProps) { - const { className, filters, isLegacy } = props; + const { className, filters } = props; const theme = useTheme(); - const RATINGS = isLegacy ? [1, 2, 3, 4, 5] : [1, 2, 3, 4]; + const RATINGS = [1, 2, 3, 4, 5]; const ratingsColors = RATINGS.map((rating: BubbleColorVal) => { const formattedMeasure = formatMeasure(rating, MetricType.Rating); @@ -50,10 +49,10 @@ export default function ColorRatingsLegend(props: ColorRatingsLegendProps) { label: formattedMeasure, value: rating, selected: !filters[rating], - backgroundColor: themeColor(isLegacy ? `bubble.legacy.${rating}` : `bubble.${rating}`)({ + backgroundColor: themeColor(`bubble.${rating}`)({ theme, }), - borderColor: themeContrast(isLegacy ? `bubble.legacy.${rating}` : `bubble.${rating}`)({ + borderColor: themeContrast(`bubble.${rating}`)({ theme, }), }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx index 49cf36bb1f1..27b3dd2832e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx @@ -46,7 +46,6 @@ import EmptyResult from './EmptyResult'; interface TreeMapViewProps { components: ComponentMeasureEnhanced[]; handleSelect: (component: ComponentMeasureIntern) => void; - isLegacyMode: boolean; metric: Metric; } @@ -57,8 +56,7 @@ interface State { } const PERCENT_SCALE_DOMAIN = [0, 25, 50, 75, 100]; -const RATING_SCALE_DOMAIN = [1, 2, 3, 4]; -const LEGACY_RATING_SCALE_DOMAIN = [1, 2, 3, 4, 5]; +const RATING_SCALE_DOMAIN = [1, 2, 3, 4, 5]; const HEIGHT = 500; const NA_COLORS: [ThemeColors, ThemeColors] = ['treeMap.NA1', 'treeMap.NA2']; @@ -69,13 +67,6 @@ const TREEMAP_COLORS: ThemeColors[] = [ 'treeMap.D', 'treeMap.E', ]; -const TREEMAP_LEGACY_COLORS: ThemeColors[] = [ - 'treeMap.legacy.A', - 'treeMap.legacy.B', - 'treeMap.legacy.C', - 'treeMap.legacy.D', - 'treeMap.legacy.E', -]; export class TreeMapView extends React.PureComponent { state: State; @@ -149,10 +140,8 @@ export class TreeMapView extends React.PureComponent { }; getMappedThemeColors = (): string[] => { - const { theme, isLegacyMode } = this.props; - return (isLegacyMode ? TREEMAP_LEGACY_COLORS : TREEMAP_COLORS).map((c) => - themeColor(c)({ theme }), - ); + const { theme } = this.props; + return TREEMAP_COLORS.map((c) => themeColor(c)({ theme })); }; getLevelColorScale = () => @@ -171,9 +160,8 @@ export class TreeMapView extends React.PureComponent { }; getRatingColorScale = () => { - const { isLegacyMode } = this.props; return scaleLinear() - .domain(isLegacyMode ? LEGACY_RATING_SCALE_DOMAIN : RATING_SCALE_DOMAIN) + .domain(RATING_SCALE_DOMAIN) .range(this.getMappedThemeColors()); }; diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index 5424ba9d481..e47f720209e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -240,11 +240,7 @@ export function banQualityGateMeasure({ measures = [], qualifier }: ComponentMea bannedMetrics.push(MetricKey.alert_status); } if (qualifier === ComponentQualifier.Application) { - bannedMetrics.push( - MetricKey.releasability_rating, - MetricKey.releasability_effort, - MetricKey.software_quality_releasability_rating, - ); + bannedMetrics.push(MetricKey.releasability_rating, MetricKey.releasability_effort); } return measures.filter((measure) => !bannedMetrics.includes(measure.metric)); } diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx index b1edb27376b..f249c8f48e4 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx @@ -52,14 +52,14 @@ it('renders correctly', async () => { `issue.clean_code_attribute_category.${issue.cleanCodeAttributeCategory}`, ).get(); expect(cctBadge).toBeInTheDocument(); - await expect(cctBadge).toHaveATooltipWithContent( + await expect(cctBadge).toHaveAPopoverWithContent( `issue.clean_code_attribute.${issue.cleanCodeAttribute}`, ); // Software Qualities const qualityBadge = byText(`software_quality.${issue.impacts[0].softwareQuality}`).get(); expect(qualityBadge).toBeInTheDocument(); - await expect(qualityBadge).toHaveATooltipWithContent('software_quality'); + await expect(qualityBadge).toHaveAPopoverWithContent('software_quality'); // Deprecated type const type = byText(`issue.type.${issue.type}`).get(); diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx deleted file mode 100644 index d4be7b73af1..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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. - */ - -import { HelperHintIcon } from 'design-system'; -import * as React from 'react'; -import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; -import SoftwareImpactSeverityIcon from '../../../components/icon-mappers/SoftwareImpactSeverityIcon'; -import { IMPACT_SEVERITIES } from '../../../helpers/constants'; -import { DocLink } from '../../../helpers/doc-links'; -import { translate } from '../../../helpers/l10n'; -import { SoftwareImpactSeverity } from '../../../types/clean-code-taxonomy'; -import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet'; - -interface Props extends CommonProps { - severities: SoftwareImpactSeverity[]; -} - -export function SeverityFacet(props: Props) { - const { severities = [], ...rest } = props; - - return ( - ( - - )} - help={ - -

{translate('issues.facet.impactSeverities.help.line1')}

-

{translate('issues.facet.impactSeverities.help.line2')}

- - } - links={[ - { - href: DocLink.CleanCodeIntroduction, - label: translate('learn_more'), - }, - ]} - > - -
- } - {...rest} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx index 749603a72d7..1f643e26f92 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx @@ -25,6 +25,7 @@ import { isPortfolioLike } from '~sonar-aligned/helpers/component'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { useAppState } from '../../../app/components/app-state/withAppStateContext'; import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; +import SeverityFacet from '../../../components/facets/SeverityFacet'; import { translate } from '../../../helpers/l10n'; import { BranchLike } from '../../../types/branch-like'; import { isApplication, isProject, isView } from '../../../types/component'; @@ -52,7 +53,6 @@ import { PrioritizedRuleFacet } from './PrioritizedRuleFacet'; import { ProjectFacet } from './ProjectFacet'; import { RuleFacet } from './RuleFacet'; import { ScopeFacet } from './ScopeFacet'; -import { SeverityFacet } from './SeverityFacet'; import { SoftwareQualityFacet } from './SoftwareQualityFacet'; import { StandardFacet } from './StandardFacet'; import { TagFacet } from './TagFacet'; @@ -204,8 +204,8 @@ export function Sidebar(props: Readonly) { onChange={props.onFilterChange} onToggle={props.onFacetToggle} open={!!openFacets.impactSeverities} - severities={query.impactSeverities} stats={facets.impactSeverities} + values={query.impactSeverities} /> diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx index abd6403a955..e878ce08907 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx @@ -48,7 +48,9 @@ it('should render correct facets for Projects with PrioritizedRules feature', () expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.cleanCodeAttributeCategories', 'issues.facet.impactSoftwareQualities', - 'issues.facet.impactSeverities', + 'coding_rules.facet.impactSeverities', + // help icon + '', 'issues.facet.types', 'issues.facet.scopes', 'issues.facet.issueStatuses', @@ -72,7 +74,9 @@ it('should render correct facets for Application', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.cleanCodeAttributeCategories', 'issues.facet.impactSoftwareQualities', - 'issues.facet.impactSeverities', + 'coding_rules.facet.impactSeverities', + // help icon + '', 'issues.facet.types', 'issues.facet.scopes', 'issues.facet.issueStatuses', @@ -94,7 +98,9 @@ it('should render correct facets for Portfolio', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.cleanCodeAttributeCategories', 'issues.facet.impactSoftwareQualities', - 'issues.facet.impactSeverities', + 'coding_rules.facet.impactSeverities', + // help icon + '', 'issues.facet.types', 'issues.facet.scopes', 'issues.facet.issueStatuses', @@ -116,7 +122,9 @@ it('should render correct facets for SubPortfolio', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.cleanCodeAttributeCategories', 'issues.facet.impactSoftwareQualities', - 'issues.facet.impactSeverities', + 'coding_rules.facet.impactSeverities', + // help icon + '', 'issues.facet.types', 'issues.facet.scopes', 'issues.facet.issueStatuses', diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx index 3918108f48b..aca69041755 100644 --- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx +++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx @@ -107,7 +107,7 @@ export const ui = { softwareQualityFacet: byRole('button', { name: 'issues.facet.impactSoftwareQualities', }), - severityFacet: byRole('button', { name: 'issues.facet.impactSeverities' }), + severityFacet: byRole('button', { name: 'coding_rules.facet.impactSeverities' }), prioritizedRuleFacet: byRole('button', { name: 'issues.facet.prioritized_rule.category' }), clearCodeCategoryFacet: byTestId('clear-issues.facet.cleanCodeAttributeCategories'), @@ -121,7 +121,7 @@ export const ui = { clearResolutionFacet: byTestId('clear-issues.facet.resolutions'), clearRuleFacet: byTestId('clear-issues.facet.rules'), clearScopeFacet: byTestId('clear-issues.facet.scopes'), - clearSeverityFacet: byTestId('clear-issues.facet.impactSeverities'), + clearSeverityFacet: byTestId('clear-coding_rules.facet.impactSeverities'), clearIssueStatusFacet: byTestId('clear-issues.facet.issueStatuses'), clearTagFacet: byTestId('clear-issues.facet.tags'), clearPrioritizedRuleFacet: byTestId('clear-issues.facet.prioritized_rule.category'), @@ -139,7 +139,9 @@ export const ui = { confirmedStatusFilter: byRole('checkbox', { name: 'issue.issue_status.CONFIRMED' }), fixedResolutionFilter: byRole('checkbox', { name: 'issue.resolution.FIXED' }), mainScopeFilter: byRole('checkbox', { name: 'issue.scope.MAIN' }), - mediumSeverityFilter: byRole('checkbox', { name: `severity.${SoftwareImpactSeverity.Medium}` }), + mediumSeverityFilter: byRole('checkbox', { + name: `severity_impact.${SoftwareImpactSeverity.Medium}`, + }), openStatusFilter: byRole('checkbox', { name: 'issue.issue_status.OPEN' }), vulnerabilityIssueTypeFilter: byRole('checkbox', { name: 'issue.type.VULNERABILITY' }), prioritizedRuleFilter: byRole('checkbox', { name: 'issues.facet.prioritized_rule' }), diff --git a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx index dcf7161a700..12ba4763629 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx @@ -197,6 +197,12 @@ export default function OverallCodeMeasuresPanel(props: Readonly + intl.formatMessage({ id: `metric.security_review_rating.tooltip.${rating}` }) + } + getLabel={(rating) => + intl.formatMessage({ id: 'metric.has_rating_X' }, { 0: rating }) + } ratingMetric={MetricKey.security_review_rating} size="md" /> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx deleted file mode 100644 index 463c20ea9f7..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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. - */ -import styled from '@emotion/styled'; -import { Tooltip } from '@sonarsource/echoes-react'; -import classNames from 'classnames'; -import { DiscreetLinkBox, themeColor, themeContrast } from 'design-system'; -import * as React from 'react'; -import { useIntl } from 'react-intl'; -import { formatMeasure } from '~sonar-aligned/helpers/measures'; -import { getComponentIssuesUrl } from '~sonar-aligned/helpers/urls'; -import { MetricType } from '~sonar-aligned/types/metrics'; -import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; -import { Branch } from '../../../types/branch-like'; -import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy'; -import { Component } from '../../../types/types'; - -export interface SoftwareImpactMeasureBreakdownCardProps { - active?: boolean; - branch?: Branch; - component: Component; - severity: SoftwareImpactSeverity; - softwareQuality: SoftwareQuality; - value?: string; -} - -export function SoftwareImpactMeasureBreakdownCard( - props: Readonly, -) { - const { softwareQuality, component, value, severity, active, branch } = props; - - const intl = useIntl(); - - const url = getComponentIssuesUrl(component.key, { - ...DEFAULT_ISSUES_QUERY, - impactSoftwareQualities: softwareQuality, - impactSeverities: severity, - branch: branch?.name, - }); - - const testId = `overview__software-impact-${softwareQuality}-severity-${severity}`; - const cardClasses = - 'sw-w-1/3 sw-px-2 sw-py-1 sw-rounded-1 sw-text-xs sw-font-semibold sw-select-none sw-flex sw-gap-1 sw-justify-center sw-items-center'; - - if (!value) { - return ( - - - - - ); - } - - return ( - - - {formatMeasure(value, MetricType.ShortInteger)} - - {intl.formatMessage({ - id: `overview.measures.software_impact.severity.${severity}`, - })} - - - - ); -} - -const StyledBreakdownCard = styled.div` - background-color: ${themeColor('overviewSoftwareImpactSeverityNeutral')}; - - &.active.HIGH { - background-color: ${themeColor('overviewSoftwareImpactSeverityHigh')}; - color: ${themeContrast('overviewSoftwareImpactSeverityHigh')}; - } - &.active.MEDIUM { - background-color: ${themeColor('overviewSoftwareImpactSeverityMedium')}; - color: ${themeContrast('overviewSoftwareImpactSeverityMedium')}; - } - &.active.LOW { - background-color: ${themeColor('overviewSoftwareImpactSeverityLow')}; - color: ${themeContrast('overviewSoftwareImpactSeverityLow')}; - } -`; -const StyledBreakdownLinkCard = StyledBreakdownCard.withComponent(DiscreetLinkBox); - -export default SoftwareImpactMeasureBreakdownCard; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx index 37c5e3248a8..96cd155eeca 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx @@ -33,15 +33,10 @@ import { import { isDefined } from '../../../helpers/types'; import { useIsLegacyCCTMode } from '../../../queries/settings'; import { Branch } from '../../../types/branch-like'; -import { - SoftwareImpactMeasureData, - SoftwareImpactSeverity, - SoftwareQuality, -} from '../../../types/clean-code-taxonomy'; +import { SoftwareImpactMeasureData, SoftwareQuality } from '../../../types/clean-code-taxonomy'; import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; import { Component, MeasureEnhanced } from '../../../types/types'; import { Status, softwareQualityToMeasure } from '../utils'; -import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard'; import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating'; export interface SoftwareImpactBreakdownCardProps { @@ -79,13 +74,6 @@ export function SoftwareImpactMeasureCard(props: Readonly measure[severity] > 0, - ); - const countTooltipOverlay = intl.formatMessage({ id: 'overview.measures.software_impact.count_tooltip', }); @@ -149,25 +137,6 @@ export function SoftwareImpactMeasureCard(props: Readonly - {measure && ( -
- {[ - SoftwareImpactSeverity.High, - SoftwareImpactSeverity.Medium, - SoftwareImpactSeverity.Low, - ].map((severity) => ( - - ))} -
- )} ); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx index 4a99bdfa447..0bc93912b37 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx @@ -44,7 +44,7 @@ import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality- import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { ComponentPropsType } from '../../../../helpers/testUtils'; -import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy'; +import { SoftwareQuality } from '../../../../types/clean-code-taxonomy'; import { ProjectAnalysisEventCategory } from '../../../../types/project-activity'; import { SettingsKey } from '../../../../types/settings'; import { CaycStatus } from '../../../../types/types'; @@ -299,53 +299,21 @@ describe('project overview', () => { await user.click(await ui.overallCodeButton.find()); - ui.expectSoftwareImpactMeasureCard( - SoftwareQuality.Security, - 'B', - { - total: 1, - [SoftwareImpactSeverity.High]: 0, - [SoftwareImpactSeverity.Medium]: 1, - [SoftwareImpactSeverity.Low]: 0, - }, - [false, true, false], - ); + ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security, 'B', 1); await ui.expectSoftwareImpactMeasureCardRatingTooltip( SoftwareQuality.Security, 'B', 'overview.measures.software_impact.improve_rating_tooltip.software_quality.SECURITY.software_quality.security.B.overview.measures.software_impact.severity.LOW.improve_tooltip', ); - ui.expectSoftwareImpactMeasureCard( - SoftwareQuality.Reliability, - 'A', - { - total: 3, - [SoftwareImpactSeverity.High]: 0, - [SoftwareImpactSeverity.Medium]: 2, - [SoftwareImpactSeverity.Low]: 1, - }, - [false, true, false], - undefined, - true, - ); + ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability, 'A', 3, undefined, true); await ui.expectSoftwareImpactMeasureCardRatingTooltip( SoftwareQuality.Reliability, 'A', 'overview.measures.software_impact.improve_rating_tooltip.A.software_quality.RELIABILITY.software_quality.reliability.A.overview.measures.software_impact.severity.LOW.improve_tooltip', ); - ui.expectSoftwareImpactMeasureCard( - SoftwareQuality.Maintainability, - 'D', - { - total: 2, - [SoftwareImpactSeverity.High]: 0, - [SoftwareImpactSeverity.Medium]: 0, - [SoftwareImpactSeverity.Low]: 1, - }, - [false, false, true], - ); + ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability, 'D', 2); await ui.expectSoftwareImpactMeasureCardRatingTooltip( SoftwareQuality.Maintainability, 'D', @@ -360,18 +328,7 @@ describe('project overview', () => { await user.click(await ui.overallCodeButton.find()); - ui.expectSoftwareImpactMeasureCard( - SoftwareQuality.Maintainability, - 'D', - { - total: 2, - [SoftwareImpactSeverity.High]: 0, - [SoftwareImpactSeverity.Medium]: 0, - [SoftwareImpactSeverity.Low]: 1, - }, - [false, false, true], - '', - ); + ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability, 'D', 2, ''); }); it('should render old measures if software impact are missing', async () => { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts b/server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts index d9832064480..9367882d9a1 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts +++ b/server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts @@ -19,11 +19,7 @@ */ import userEvent from '@testing-library/user-event'; import { byLabelText, byRole, byTestId, byText } from '~sonar-aligned/helpers/testSelector'; -import { - SoftwareImpactMeasureData, - SoftwareImpactSeverity, - SoftwareQuality, -} from '../../../types/clean-code-taxonomy'; +import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy'; export const getPageObjects = () => { const user = userEvent.setup(); @@ -41,8 +37,7 @@ export const getPageObjects = () => { expectSoftwareImpactMeasureCard: ( softwareQuality: SoftwareQuality, rating?: string, - data?: SoftwareImpactMeasureData, - severitiesActiveState?: boolean[], + total?: number, branch = 'master', failed = false, ) => { @@ -59,58 +54,17 @@ export const getPageObjects = () => { byText(rating, { exact: true }).get(ui.softwareImpactMeasureCard(softwareQuality).get()), ).toBeInTheDocument(); } - if (data) { + if (total !== undefined) { const branchQuery = branch ? `&branch=${branch}` : ''; expect( byRole('link', { - name: `overview.measures.software_impact.see_list_of_x_open_issues.${data.total}.software_quality.${softwareQuality}`, + name: `overview.measures.software_impact.see_list_of_x_open_issues.${total}.software_quality.${softwareQuality}`, }).get(), ).toHaveAttribute( 'href', `/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=${softwareQuality}${branchQuery}&id=foo`, ); - expect( - byRole('link', { - name: `overview.measures.software_impact.severity.see_x_open_issues.${ - data[SoftwareImpactSeverity.High] - }.software_quality.${softwareQuality}.overview.measures.software_impact.severity.HIGH.tooltip`, - }).get(), - ).toHaveAttribute( - 'href', - `/project/issues?issueStatuses=OPEN%2CCONFIRMED&impactSoftwareQualities=${softwareQuality}&impactSeverities=${SoftwareImpactSeverity.High}${branchQuery}&id=foo`, - ); - expect( - byRole('link', { - name: `overview.measures.software_impact.severity.see_x_open_issues.${ - data[SoftwareImpactSeverity.Medium] - }.software_quality.${softwareQuality}.overview.measures.software_impact.severity.MEDIUM.tooltip`, - }).get(), - ).toBeInTheDocument(); - expect( - byRole('link', { - name: `overview.measures.software_impact.severity.see_x_open_issues.${ - data[SoftwareImpactSeverity.Low] - }.software_quality.${softwareQuality}.overview.measures.software_impact.severity.LOW.tooltip`, - }).get(), - ).toBeInTheDocument(); - } - if (severitiesActiveState) { - ui.expectSoftwareImpactMeasureBreakdownCard( - softwareQuality, - SoftwareImpactSeverity.High, - severitiesActiveState[0], - ); - ui.expectSoftwareImpactMeasureBreakdownCard( - softwareQuality, - SoftwareImpactSeverity.Medium, - severitiesActiveState[1], - ); - ui.expectSoftwareImpactMeasureBreakdownCard( - softwareQuality, - SoftwareImpactSeverity.Low, - severitiesActiveState[2], - ); } }, expectSoftwareImpactMeasureCardToHaveOldMeasures: ( diff --git a/server/sonar-web/src/main/js/apps/overview/utils.tsx b/server/sonar-web/src/main/js/apps/overview/utils.tsx index fa7e2786ea5..0d94c172778 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.tsx +++ b/server/sonar-web/src/main/js/apps/overview/utils.tsx @@ -68,9 +68,7 @@ export const BRANCH_OVERVIEW_METRICS: string[] = [ MetricKey.security_hotspots_reviewed, MetricKey.new_security_hotspots_reviewed, MetricKey.security_review_rating, - MetricKey.software_quality_security_review_rating, MetricKey.new_security_review_rating, - MetricKey.new_software_quality_security_review_rating, // code smells MetricKey.code_smells, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx index 80b1d177e92..f059e203627 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx @@ -253,8 +253,7 @@ export default class ProjectActivityGraphs extends React.PureComponent { expect(await ui.graphs.findAll()).toHaveLength(1); expect(ui.metricChangedInfoBtn.get()).toBeInTheDocument(); expect(ui.gapInfoMessage.get()).toBeInTheDocument(); - expect(byText('E').query()).not.toBeInTheDocument(); }); it('should not show old rating if new one was always there', async () => { @@ -652,10 +651,9 @@ describe('ratings', () => { expect(await ui.graphs.findAll()).toHaveLength(1); expect(ui.metricChangedInfoBtn.query()).not.toBeInTheDocument(); expect(ui.gapInfoMessage.query()).not.toBeInTheDocument(); - expect(byText('E').query()).not.toBeInTheDocument(); }); - it('should show E if no new metrics', async () => { + it('should not show change info button if no new metrics', async () => { timeMachineHandler.setMeasureHistory([ mockMeasureHistory({ metric: MetricKey.reliability_rating, @@ -686,10 +684,9 @@ describe('ratings', () => { expect(await ui.graphs.findAll()).toHaveLength(1); expect(ui.metricChangedInfoBtn.query()).not.toBeInTheDocument(); expect(ui.gapInfoMessage.query()).not.toBeInTheDocument(); - expect(byText('E').get()).toBeInTheDocument(); }); - it('should not show gaps message and metric change button, but should show E in legacy mode', async () => { + it('should not show gaps message and metric change button in legacy mode', async () => { settingsHandler.set(SettingsKey.LegacyMode, 'true'); timeMachineHandler.setMeasureHistory([ mockMeasureHistory({ @@ -746,7 +743,6 @@ describe('ratings', () => { expect(await ui.graphs.findAll()).toHaveLength(1); expect(ui.metricChangedInfoBtn.query()).not.toBeInTheDocument(); expect(ui.gapInfoMessage.query()).not.toBeInTheDocument(); - expect(byText('E').get()).toBeInTheDocument(); }); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx index 63fe3f135e9..927df18e7aa 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx @@ -88,20 +88,20 @@ it('should show legacy filters', async () => { settingsHandler.set(SettingsKey.LegacyMode, 'true'); renderPageSidebar(); - expect(await screen.findAllByText('E')).toHaveLength(4); - expect(screen.queryByText(/projects.facets.rating_option/)).not.toBeInTheDocument(); - expect(screen.queryByText('projects.facets.maintainability.description')).not.toBeInTheDocument(); - expect(screen.queryByText('projects.facets.security_review.description')).not.toBeInTheDocument(); + expect(await screen.findAllByText(/projects.facets.rating_option/)).toHaveLength(20); + expect(screen.getByText('projects.facets.rating_option.security.legacy.1')).toBeInTheDocument(); + expect( + screen.getByText('projects.facets.rating_option.reliability.legacy.1'), + ).toBeInTheDocument(); }); it('should show non legacy filters', async () => { settingsHandler.set(SettingsKey.LegacyMode, 'false'); renderPageSidebar(); - expect(await screen.findAllByText(/projects.facets.rating_option/)).toHaveLength(16); - expect(screen.queryAllByText('E')).toHaveLength(0); - expect(screen.getByText('projects.facets.maintainability.description')).toBeInTheDocument(); - expect(screen.getByText('projects.facets.security_review.description')).toBeInTheDocument(); + expect(await screen.findAllByText(/projects.facets.rating_option/)).toHaveLength(20); + expect(screen.getByText('projects.facets.rating_option.security.1')).toBeInTheDocument(); + expect(screen.getByText('projects.facets.rating_option.reliability.1')).toBeInTheDocument(); }); function renderPageSidebar(overrides: Partial = {}, currentUser?: CurrentUser) { diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx index e062e396102..998ac41cc8c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx @@ -38,7 +38,7 @@ const MEASURES = { [MetricKey.reliability_rating]: '1.0', [MetricKey.security_rating]: '1.0', [MetricKey.sqale_rating]: '1.0', - [MetricKey.security_review_rating]: '1.0', + [MetricKey.security_review_rating]: '3.0', [MetricKey.new_bugs]: '12', }; @@ -123,7 +123,7 @@ describe('upgrade scenario (awaiting scan)', () => { }), [MetricKey.security_review_rating]: mockMeasure({ metric: MetricKey.security_review_rating, - value: '1', + value: '3', }), }; @@ -140,9 +140,9 @@ describe('upgrade scenario (awaiting scan)', () => { metric: MetricKey.software_quality_security_rating, value: '2', }), - [MetricKey.software_quality_security_review_rating]: mockMeasure({ - metric: MetricKey.software_quality_security_review_rating, - value: '2', + [MetricKey.security_review_rating]: mockMeasure({ + metric: MetricKey.security_review_rating, + value: '3', }), }; beforeEach(() => { @@ -174,7 +174,6 @@ describe('upgrade scenario (awaiting scan)', () => { [MetricKey.software_quality_maintainability_rating]: '2', [MetricKey.software_quality_reliability_rating]: '2', [MetricKey.software_quality_security_rating]: '2', - [MetricKey.software_quality_security_review_rating]: '2', [MetricKey.code_smells]: '4', [MetricKey.bugs]: '5', [MetricKey.vulnerabilities]: '6', @@ -183,7 +182,8 @@ describe('upgrade scenario (awaiting scan)', () => { expect(screen.getByText('1')).toBeInTheDocument(); expect(screen.getByText('2')).toBeInTheDocument(); expect(screen.getByText('3')).toBeInTheDocument(); - await waitFor(() => expect(screen.getAllByText('B')).toHaveLength(4)); + await waitFor(() => expect(screen.getAllByText('B')).toHaveLength(3)); + await waitFor(() => expect(screen.getAllByText('C')).toHaveLength(1)); expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); expect(screen.queryByText('4')).not.toBeInTheDocument(); expect(screen.queryByText('5')).not.toBeInTheDocument(); @@ -208,7 +208,8 @@ describe('upgrade scenario (awaiting scan)', () => { expect(screen.getByText('4')).toBeInTheDocument(); expect(screen.getByText('5')).toBeInTheDocument(); expect(screen.getByText('6')).toBeInTheDocument(); - expect(screen.getAllByText('A')).toHaveLength(4); + expect(screen.getAllByText('A')).toHaveLength(3); + expect(screen.getAllByText('C')).toHaveLength(1); }); it('should display awaiting analysis badge, show new software qualities, but old ratings', async () => { @@ -236,7 +237,8 @@ describe('upgrade scenario (awaiting scan)', () => { expect(screen.queryByText('4')).not.toBeInTheDocument(); expect(screen.queryByText('5')).not.toBeInTheDocument(); expect(screen.queryByText('6')).not.toBeInTheDocument(); - await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(3)); + expect(screen.getAllByText('C')).toHaveLength(1); }); it('should display awaiting analysis badge and show the old measures for Application', async () => { @@ -296,7 +298,8 @@ describe('upgrade scenario (awaiting scan)', () => { expect(screen.getByText('4')).toBeInTheDocument(); expect(screen.getByText('5')).toBeInTheDocument(); expect(screen.getByText('6')).toBeInTheDocument(); - await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(3)); + expect(screen.getAllByText('C')).toHaveLength(1); expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); }); @@ -323,7 +326,6 @@ describe('upgrade scenario (awaiting scan)', () => { [MetricKey.software_quality_maintainability_rating]: '2', [MetricKey.software_quality_reliability_rating]: '2', [MetricKey.software_quality_security_rating]: '2', - [MetricKey.software_quality_security_review_rating]: '2', [MetricKey.code_smells]: '4', [MetricKey.bugs]: '5', [MetricKey.vulnerabilities]: '6', @@ -335,7 +337,8 @@ describe('upgrade scenario (awaiting scan)', () => { expect(screen.queryByText('1')).not.toBeInTheDocument(); expect(screen.queryByText('2')).not.toBeInTheDocument(); expect(screen.queryByText('3')).not.toBeInTheDocument(); - await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(4)); + await waitFor(() => expect(screen.getAllByText('A')).toHaveLength(3)); + expect(screen.getAllByText('C')).toHaveLength(1); expect(screen.queryByText('B')).not.toBeInTheDocument(); expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx index 5dbbfc52654..cd9aad48051 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Spinner } from '@sonarsource/echoes-react'; import { MetricsRatingBadge, RatingEnum } from 'design-system'; import * as React from 'react'; import { useIntl } from 'react-intl'; @@ -37,9 +38,8 @@ interface Props { value?: any; } -export default function RatingFacet(props: Props) { +export default function RatingFacet(props: Readonly) { const { facet, maxFacetValue, name, property, value } = props; - const { data: isLegacy } = useIsLegacyCCTMode(); const renderAccessibleLabel = React.useCallback( (option: number) => { @@ -65,14 +65,14 @@ export default function RatingFacet(props: Props) { facet={facet} header={translate('metric_domain', name)} description={ - !isLegacy && hasDescription(property) + hasDescription(property) ? translate(`projects.facets.${property.replace('new_', '')}.description`) : undefined } highlightUnder={1} maxFacetValue={maxFacetValue} onQueryChange={props.onQueryChange} - options={isLegacy ? [1, 2, 3, 4, 5] : [1, 2, 3, 4]} + options={[1, 2, 3, 4, 5]} property={property} renderAccessibleLabel={renderAccessibleLabel} renderOption={(option) => renderOption(option, property)} @@ -93,25 +93,24 @@ function RatingOption({ option, property, }: Readonly<{ option: string | number; property: string }>) { - const { data: isLegacy } = useIsLegacyCCTMode(); + const { data: isLegacy, isLoading } = useIsLegacyCCTMode(); const intl = useIntl(); const ratingFormatted = formatMeasure(option, MetricType.Rating); + const propertyWithoutPrefix = property.replace('new_', ''); + const isSecurityOrReliability = ['security', 'reliability'].includes(propertyWithoutPrefix); return ( - <> + - {!isLegacy && ( - - {intl.formatMessage({ - id: `projects.facets.rating_option.${property.replace('new_', '')}.${option}`, - })} - - )} - + + {intl.formatMessage({ + id: `projects.facets.rating_option.${propertyWithoutPrefix}${isLegacy && isSecurityOrReliability ? '.legacy' : ''}.${option}`, + })} + + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts index 21ec392bfbe..709e2626f52 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.ts +++ b/server/sonar-web/src/main/js/apps/projects/utils.ts @@ -102,7 +102,6 @@ export const METRICS = [ MetricKey.software_quality_maintainability_rating, MetricKey.security_hotspots_reviewed, MetricKey.security_review_rating, - MetricKey.software_quality_security_review_rating, MetricKey.duplicated_lines_density, MetricKey.coverage, MetricKey.ncloc, @@ -138,8 +137,8 @@ export const LEGACY_FACETS = [ export const FACETS = [ MetricKey.software_quality_reliability_rating, MetricKey.software_quality_security_rating, - MetricKey.software_quality_security_review_rating, MetricKey.software_quality_maintainability_rating, + MetricKey.security_review_rating, MetricKey.coverage, MetricKey.duplicated_lines_density, MetricKey.ncloc, @@ -166,8 +165,8 @@ export const LEGACY_LEAK_FACETS = [ export const LEAK_FACETS = [ MetricKey.new_software_quality_reliability_rating, MetricKey.new_software_quality_security_rating, - MetricKey.new_software_quality_security_review_rating, MetricKey.new_software_quality_maintainability_rating, + MetricKey.new_security_review_rating, MetricKey.new_coverage, MetricKey.new_duplicated_lines_density, MetricKey.new_lines, @@ -318,8 +317,6 @@ export const propertyToMetricMap: Dict = { new_reliability: 'new_software_quality_reliability_rating', security: 'software_quality_security_rating', new_security: 'new_software_quality_security_rating', - security_review: 'software_quality_security_review_rating', - new_security_review: 'new_software_quality_security_review_rating', maintainability: 'software_quality_maintainability_rating', new_maintainability: 'new_software_quality_maintainability_rating', }; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx index 4f0bfadf919..f97e31fa685 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx @@ -50,8 +50,6 @@ const FORBIDDEN_METRICS: string[] = [ MetricKey.new_software_quality_reliability_rating, MetricKey.software_quality_security_rating, MetricKey.new_software_quality_security_rating, - MetricKey.software_quality_security_review_rating, - MetricKey.new_software_quality_security_review_rating, MetricKey.effort_to_reach_software_quality_maintainability_rating_a, MetricKey.software_quality_maintainability_remediation_effort, MetricKey.new_software_quality_maintainability_remediation_effort, diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx index 028c1ccd46e..1317281641b 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx @@ -41,7 +41,6 @@ interface Props { graphEndDate?: Date; graphStartDate?: Date; isCustom?: boolean; - isLegacy?: boolean; leakPeriodDate?: Date; measuresHistory: MeasureHistory[]; metricsType: string; @@ -69,7 +68,6 @@ export default function GraphHistory(props: Readonly) { series, showAreas, graphDescription, - isLegacy, } = props; const [tooltipIdx, setTooltipIdx] = React.useState(undefined); const [tooltipXPos, setTooltipXPos] = React.useState(undefined); @@ -124,7 +122,6 @@ export default function GraphHistory(props: Readonly) { splitPointDate={measuresHistory.find((m) => m.splitPointDate)?.splitPointDate} metricType={metricsType} selectedDate={selectedDate} - isLegacy={isLegacy} series={series} showAreas={showAreas} startDate={graphStartDate} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx index 9be16362121..5bf2137e1d8 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx @@ -33,7 +33,6 @@ interface Props { graphEndDate?: Date; graphStartDate?: Date; graphs: Serie[][]; - isLegacy?: boolean; leakPeriodDate?: Date; loading: boolean; measuresHistory: MeasureHistory[]; @@ -67,8 +66,7 @@ export default class GraphsHistory extends React.PureComponent { }; render() { - const { analyses, graph, loading, series, ariaLabel, canShowDataAsTable, isLegacy } = - this.props; + const { analyses, graph, loading, series, ariaLabel, canShowDataAsTable } = this.props; const isCustom = isCustomGraph(graph); if (loading) { @@ -107,7 +105,6 @@ export default class GraphsHistory extends React.PureComponent { graphStartDate={this.props.graphStartDate} isCustom={isCustom} key={idx} - isLegacy={isLegacy} leakPeriodDate={this.props.leakPeriodDate} measuresHistory={this.props.measuresHistory} metricsType={getSeriesMetricType(graphSeries)} diff --git a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx index 632410a2370..2f86e9e75ed 100644 --- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx +++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx @@ -51,7 +51,6 @@ export interface PropsWithoutTheme { height: number; hideGrid?: boolean; hideXAxis?: boolean; - isLegacy?: boolean; leakPeriodDate?: Date; // used to avoid same y ticks labels maxYTicksCount?: number; @@ -145,10 +144,7 @@ export class AdvancedTimelineClass extends React.PureComponent { } getRatingScale = (availableHeight: number) => { - const { isLegacy } = this.props; - return scalePoint() - .domain(isLegacy ? [5, 4, 3, 2, 1] : [4, 3, 2, 1]) - .range([availableHeight, 0]); + return scalePoint().domain([5, 4, 3, 2, 1]).range([availableHeight, 0]); }; getLevelScale = (availableHeight: number) => { diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/AdvancedTimeline-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/AdvancedTimeline-test.tsx index fbf0cad0c04..ed7dfb4d301 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/AdvancedTimeline-test.tsx +++ b/server/sonar-web/src/main/js/components/charts/__tests__/AdvancedTimeline-test.tsx @@ -61,18 +61,9 @@ it('should render correctly', () => { checkSnapShot({ zoomSpeed: 2 }, 'zoomSpeed'); checkSnapShot({ leakPeriodDate: new Date('2019-10-02T00:00:00.000Z') }, 'leakPeriodDate'); checkSnapShot({ basisCurve: true }, 'basisCurve'); - checkSnapShot({ isLegacy: false }, 'not legacy'); checkSnapShot( - { isLegacy: false, splitPointDate: new Date('2019-10-02T00:00:00.000Z') }, - 'not legacy + split point, but not Rating', - ); - checkSnapShot( - { - isLegacy: false, - splitPointDate: new Date('2019-10-02T00:00:00.000Z'), - metricType: MetricType.Rating, - }, - 'not legacy + split point', + { splitPointDate: new Date('2019-10-02T00:00:00.000Z') }, + 'split point, but not Rating', ); }); @@ -113,7 +104,6 @@ function renderComponent(props?: Partial) { ]} width={100} zoomSpeed={1} - isLegacy {...props} /> , diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap index 93c34d14707..b44f642ade9 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap @@ -1913,171 +1913,7 @@ exports[`should render correctly: no height 1`] = `null`; exports[`should render correctly: no width 1`] = `null`; -exports[`should render correctly: not legacy + split point 1`] = ` - - - - - - - - - - - - - - - - - - - October - - - 06 AM - - - 12 PM - - - 06 PM - - - Wed 02 - - - 06 AM - - - 12 PM - - - 06 PM - - - - - - - - - - - - - -`; - -exports[`should render correctly: not legacy + split point, but not Rating 1`] = ` +exports[`should render correctly: rating metric 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2209,8 +1946,8 @@ exports[`should render correctly: not legacy + split point, but not Rating 1`] = class="line-chart-grid" x1="0" x2="40" - y1="3.1999999999999993" - y2="3.1999999999999993" + y1="12" + y2="12" /> @@ -2218,8 +1955,8 @@ exports[`should render correctly: not legacy + split point, but not Rating 1`] = class="line-chart-grid" x1="0" x2="40" - y1="1.6000000000000023" - y2="1.6000000000000023" + y1="6" + y2="6" /> @@ -2311,13 +2048,13 @@ exports[`should render correctly: not legacy + split point, but not Rating 1`] = @@ -2325,7 +2062,7 @@ exports[`should render correctly: not legacy + split point, but not Rating 1`] = - `; -exports[`should render correctly: not legacy 1`] = ` +exports[`should render correctly: selected date 1`] = ` - - - -`; - -exports[`should render correctly: rating metric 1`] = ` - - - - - - - - - - - - - - - - - - - - - October - - - 06 AM - - - 12 PM - - - 06 PM - - - Wed 02 - - - 06 AM - - - 12 PM - - - 06 PM - - - - - - - @@ -2778,7 +2367,7 @@ exports[`should render correctly: rating metric 1`] = ` `; -exports[`should render correctly: selected date 1`] = ` +exports[`should render correctly: split point, but not Rating 1`] = ` - - - - - + `; diff --git a/server/sonar-web/src/main/js/components/facets/Facet.tsx b/server/sonar-web/src/main/js/components/facets/Facet.tsx new file mode 100644 index 00000000000..0273f0f4739 --- /dev/null +++ b/server/sonar-web/src/main/js/components/facets/Facet.tsx @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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. + */ +import classNames from 'classnames'; +import { FacetBox, FacetItem } from 'design-system'; +import { orderBy, sortBy, without } from 'lodash'; +import * as React from 'react'; +import { formatMeasure } from '~sonar-aligned/helpers/measures'; +import { MetricType } from '~sonar-aligned/types/metrics'; +import { FacetKey } from '../../apps/coding-rules/query'; +import { FacetItemsList } from '../../apps/issues/sidebar/FacetItemsList'; +import { MultipleSelectionHint } from '../../apps/issues/sidebar/MultipleSelectionHint'; +import { translate } from '../../helpers/l10n'; +import { Dict } from '../../types/types'; +import Tooltip from '../controls/Tooltip'; + +export interface BasicProps { + fetching?: boolean; + help?: React.ReactNode; + onChange: (changes: Dict) => void; + onToggle: (facet: FacetKey) => void; + open: boolean; + stats?: Dict; + values: string[]; +} + +interface Props extends BasicProps { + disabled?: boolean; + disabledHelper?: string; + options?: string[]; + property: FacetKey; + renderFooter?: () => React.ReactNode; + renderName?: (value: string, disabled: boolean) => React.ReactNode; + renderTextName?: (value: string) => string; + singleSelection?: boolean; +} + +export default class Facet extends React.PureComponent { + handleItemClick = (itemValue: string, multiple: boolean) => { + const { values } = this.props; + let newValue; + if (this.props.singleSelection) { + const value = values.length ? values[0] : undefined; + newValue = itemValue === value ? undefined : itemValue; + } else if (multiple) { + newValue = orderBy( + values.includes(itemValue) ? without(values, itemValue) : [...values, itemValue], + ); + } else { + newValue = values.includes(itemValue) && values.length < 2 ? [] : [itemValue]; + } + this.props.onChange({ [this.props.property]: newValue }); + }; + + handleHeaderClick = () => this.props.onToggle(this.props.property); + + handleClear = () => this.props.onChange({ [this.props.property]: [] }); + + getStat = (value: string) => this.props.stats && this.props.stats[value]; + + renderItem = (value: string) => { + const active = this.props.values.includes(value); + const stat = this.getStat(value); + const disabled = stat === 0 || typeof stat === 'undefined'; + const { renderName = defaultRenderName, renderTextName = defaultRenderName } = this.props; + + return ( + + ); + }; + + render() { + const { + disabled, + disabledHelper, + open, + property, + renderTextName = defaultRenderName, + stats, + help, + values, + fetching, + } = this.props; + const items = + this.props.options || + (stats && + sortBy( + Object.keys(stats), + (key) => -stats[key], + (key) => renderTextName(key).toLowerCase(), + )); + const headerId = `facet_${property}`; + const nbSelectableItems = + items?.filter((item) => (stats ? stats[item] : undefined)).length ?? 0; + const nbSelectedItems = values.length; + + return ( + + {open && items !== undefined && ( + {items.map(this.renderItem)} + )} + + {open && this.props.renderFooter !== undefined && this.props.renderFooter()} + + + + ); + } +} + +function defaultRenderName(value: string) { + return value; +} diff --git a/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx b/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx new file mode 100644 index 00000000000..3f7cae9eca6 --- /dev/null +++ b/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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. + */ + +import { Popover } from '@sonarsource/echoes-react'; +import { BareButton, HelperHintIcon } from 'design-system'; +import * as React from 'react'; +import { useIntl } from 'react-intl'; +import { IMPACT_SEVERITIES } from '../../helpers/constants'; +import { DocLink } from '../../helpers/doc-links'; +import { translate } from '../../helpers/l10n'; +import DocumentationLink from '../common/DocumentationLink'; +import SoftwareImpactSeverityIcon from '../icon-mappers/SoftwareImpactSeverityIcon'; +import Facet, { BasicProps } from './Facet'; + +export default function SeverityFacet(props: Readonly) { + const intl = useIntl(); + const renderName = React.useCallback( + (severity: string, disabled: boolean) => ( +
+ + {translate('severity_impact', severity)} +
+ ), + [], + ); + + const renderTextName = React.useCallback( + (severity: string) => translate('severity_impact', severity), + [], + ); + + return ( + +

{intl.formatMessage({ id: 'severity_impact.help.line1' })}

+

{intl.formatMessage({ id: 'severity_impact.help.line2' })}

+ + } + footer={ + + {intl.formatMessage({ id: 'learn_more' })} + + } + > + + + + + } + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/icon-mappers/SoftwareImpactSeverityIcon.tsx b/server/sonar-web/src/main/js/components/icon-mappers/SoftwareImpactSeverityIcon.tsx index 61251a5db8c..9d1cebd666b 100644 --- a/server/sonar-web/src/main/js/components/icon-mappers/SoftwareImpactSeverityIcon.tsx +++ b/server/sonar-web/src/main/js/components/icon-mappers/SoftwareImpactSeverityIcon.tsx @@ -19,7 +19,9 @@ */ import { IconProps, + SoftwareImpactSeverityBlockerIcon, SoftwareImpactSeverityHighIcon, + SoftwareImpactSeverityInfoIcon, SoftwareImpactSeverityLowIcon, SoftwareImpactSeverityMediumIcon, } from 'design-system'; @@ -33,10 +35,14 @@ interface Props extends IconProps { severity: string | null | undefined; } +const defaultIconSize = 14; + const severityIcons: Dict<(props: IconProps) => React.ReactElement> = { + [SoftwareImpactSeverity.Blocker]: SoftwareImpactSeverityBlockerIcon, [SoftwareImpactSeverity.High]: SoftwareImpactSeverityHighIcon, [SoftwareImpactSeverity.Medium]: SoftwareImpactSeverityMediumIcon, [SoftwareImpactSeverity.Low]: SoftwareImpactSeverityLowIcon, + [SoftwareImpactSeverity.Info]: SoftwareImpactSeverityInfoIcon, }; export default function SoftwareImpactSeverityIcon({ severity, ...iconProps }: Readonly) { @@ -45,5 +51,12 @@ export default function SoftwareImpactSeverityIcon({ severity, ...iconProps }: R } const DesiredIcon = severityIcons[severity]; - return ; + return ( + + ); } diff --git a/server/sonar-web/src/main/js/components/measure/utils.ts b/server/sonar-web/src/main/js/components/measure/utils.ts index 60e1437baf7..f37449d3e98 100644 --- a/server/sonar-web/src/main/js/components/measure/utils.ts +++ b/server/sonar-web/src/main/js/components/measure/utils.ts @@ -28,7 +28,6 @@ export const KNOWN_RATINGS = [ MetricKey.software_quality_maintainability_rating, MetricKey.software_quality_reliability_rating, MetricKey.software_quality_security_rating, - MetricKey.software_quality_security_review_rating, 'maintainability_rating', // Needed to provide the label for "new_maintainability_rating" ]; diff --git a/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx b/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx index 20ec0c2b7df..9e386598774 100644 --- a/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx +++ b/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx @@ -18,12 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Pill } from 'design-system'; +import { Popover } from '@sonarsource/echoes-react'; +import { Pill, PillVariant } from 'design-system'; import React from 'react'; -import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; import { DocLink } from '../../helpers/doc-links'; import { translate } from '../../helpers/l10n'; import { CleanCodeAttribute, CleanCodeAttributeCategory } from '../../types/clean-code-taxonomy'; +import DocumentationLink from '../common/DocumentationLink'; export interface Props { className?: string; @@ -36,35 +37,26 @@ export function CleanCodeAttributePill(props: Readonly) { const { className, cleanCodeAttributeCategory, cleanCodeAttribute, type = 'issue' } = props; return ( - -

- {translate( - type, - cleanCodeAttribute ? 'clean_code_attribute' : 'clean_code_attribute_category', - cleanCodeAttribute ?? cleanCodeAttributeCategory, - 'title', - )} -

-

- {translate( - 'issue', - cleanCodeAttribute ? 'clean_code_attribute' : 'clean_code_attribute_category', - cleanCodeAttribute ?? cleanCodeAttributeCategory, - 'advice', - )} -

- + + {translate('learn_more')} + } - links={[ - { - href: DocLink.CleanCodeIntroduction, - label: translate('learn_more'), - }, - ]} > - + {translate(type, 'clean_code_attribute_category', cleanCodeAttributeCategory)} @@ -72,6 +64,6 @@ export function CleanCodeAttributePill(props: Readonly) { | {translate(type, 'clean_code_attribute', cleanCodeAttribute)} )} -
+ ); } diff --git a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx index f0ae5848ac2..88b531e6232 100644 --- a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx +++ b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx @@ -18,14 +18,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Popover } from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import { Pill } from 'design-system'; +import { Pill, PillVariant } from 'design-system'; +import { noop } from 'lodash'; import React from 'react'; import { FormattedMessage } from 'react-intl'; -import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; import { DocLink } from '../../helpers/doc-links'; import { translate } from '../../helpers/l10n'; import { SoftwareImpactSeverity } from '../../types/clean-code-taxonomy'; +import DocumentationLink from '../common/DocumentationLink'; import SoftwareImpactSeverityIcon from '../icon-mappers/SoftwareImpactSeverityIcon'; export interface Props { @@ -39,34 +41,50 @@ export default function SoftwareImpactPill(props: Props) { const { className, severity, quality, type = 'issue' } = props; const variant = { - [SoftwareImpactSeverity.High]: 'danger', - [SoftwareImpactSeverity.Medium]: 'warning', - [SoftwareImpactSeverity.Low]: 'info', - }[severity] as 'danger' | 'warning' | 'info'; + [SoftwareImpactSeverity.Blocker]: PillVariant.Critical, + [SoftwareImpactSeverity.High]: PillVariant.Danger, + [SoftwareImpactSeverity.Medium]: PillVariant.Warning, + [SoftwareImpactSeverity.Low]: PillVariant.Caution, + [SoftwareImpactSeverity.Info]: PillVariant.Info, + }[severity]; return ( - + + +

+ {translate('severity_impact.help.line1')} + {translate('severity_impact.help.line2')} +

+ + } + footer={ + + {translate('learn_more')} + } - links={[ - { - href: DocLink.CleanCodeIntroduction, - label: translate('learn_more'), - }, - ]} > - + {quality} - + -
+ ); } diff --git a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx index 3bc09b6b130..476e9cd98bf 100644 --- a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx +++ b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx @@ -34,9 +34,11 @@ interface SoftwareImpactPillListProps extends React.HTMLAttributes ({ date: parseDateFn(historyItem.date), - value: - softwareQualityMeasuresMap.size > 0 && historyItem.value === '5.0' - ? '4.0' - : historyItem.value, + value: historyItem.value, }); return historyDataFiltered.map((measure) => { diff --git a/server/sonar-web/src/main/js/helpers/constants.ts b/server/sonar-web/src/main/js/helpers/constants.ts index ce344a72320..2e70556dfaa 100644 --- a/server/sonar-web/src/main/js/helpers/constants.ts +++ b/server/sonar-web/src/main/js/helpers/constants.ts @@ -112,11 +112,9 @@ export const LEAK_OLD_TAXONOMY_METRICS = [ ]; export const OLD_TAXONOMY_RATINGS = [ - MetricKey.releasability_rating, MetricKey.sqale_rating, MetricKey.security_rating, MetricKey.reliability_rating, - MetricKey.security_review_rating, MetricKey.sqale_index, MetricKey.reliability_remediation_effort, MetricKey.security_remediation_effort, @@ -128,7 +126,6 @@ export const LEAK_OLD_TAXONOMY_RATINGS = [ MetricKey.new_maintainability_rating, MetricKey.new_security_rating, MetricKey.new_reliability_rating, - MetricKey.new_security_review_rating, MetricKey.new_technical_debt, MetricKey.new_security_remediation_effort, MetricKey.new_reliability_remediation_effort, @@ -191,9 +188,6 @@ export const DEPRECATED_ACTIVITY_METRICS = [ ]; export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record = { - [MetricKey.releasability_rating]: MetricKey.software_quality_releasability_rating, - [MetricKey.releasability_rating_distribution]: - MetricKey.software_quality_releasability_rating_distribution, [MetricKey.sqale_rating]: MetricKey.software_quality_maintainability_rating, [MetricKey.maintainability_rating_distribution]: MetricKey.software_quality_maintainability_rating_distribution, @@ -202,9 +196,6 @@ export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record = { [MetricKey.reliability_rating]: MetricKey.software_quality_reliability_rating, [MetricKey.reliability_rating_distribution]: MetricKey.software_quality_reliability_rating_distribution, - [MetricKey.security_review_rating]: MetricKey.software_quality_security_review_rating, - [MetricKey.security_review_rating_distribution]: - MetricKey.software_quality_security_review_rating_distribution, [MetricKey.reliability_remediation_effort]: MetricKey.software_quality_reliability_remediation_effort, [MetricKey.security_remediation_effort]: MetricKey.software_quality_security_remediation_effort, @@ -214,20 +205,14 @@ export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record = { MetricKey.effort_to_reach_software_quality_maintainability_rating_a, [MetricKey.last_change_on_maintainability_rating]: MetricKey.last_change_on_software_quality_maintainability_rating, - [MetricKey.last_change_on_releasability_rating]: - MetricKey.last_change_on_software_quality_releasability_rating, [MetricKey.last_change_on_reliability_rating]: MetricKey.last_change_on_software_quality_reliability_rating, [MetricKey.last_change_on_security_rating]: MetricKey.last_change_on_software_quality_security_rating, - [MetricKey.last_change_on_security_review_rating]: - MetricKey.last_change_on_software_quality_security_review_rating, [MetricKey.maintainability_rating_effort]: MetricKey.software_quality_maintainability_rating_effort, [MetricKey.reliability_rating_effort]: MetricKey.software_quality_reliability_rating_effort, [MetricKey.security_rating_effort]: MetricKey.software_quality_security_rating_effort, - [MetricKey.security_review_rating_effort]: - MetricKey.software_quality_security_review_rating_effort, [MetricKey.new_maintainability_rating]: MetricKey.new_software_quality_maintainability_rating, [MetricKey.new_maintainability_rating_distribution]: MetricKey.new_software_quality_maintainability_rating_distribution, @@ -237,9 +222,6 @@ export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record = { [MetricKey.new_reliability_rating]: MetricKey.new_software_quality_reliability_rating, [MetricKey.new_reliability_rating_distribution]: MetricKey.new_software_quality_reliability_rating_distribution, - [MetricKey.new_security_review_rating]: MetricKey.new_software_quality_security_review_rating, - [MetricKey.new_security_review_rating_distribution]: - MetricKey.new_software_quality_security_review_rating_distribution, [MetricKey.new_technical_debt]: MetricKey.new_software_quality_maintainability_remediation_effort, [MetricKey.new_reliability_remediation_effort]: MetricKey.new_software_quality_reliability_remediation_effort, @@ -249,11 +231,9 @@ export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record = { }; export const SOFTWARE_QUALITY_RATING_METRICS = [ - MetricKey.software_quality_releasability_rating, MetricKey.software_quality_maintainability_rating, MetricKey.software_quality_security_rating, MetricKey.software_quality_reliability_rating, - MetricKey.software_quality_security_review_rating, MetricKey.software_quality_maintainability_remediation_effort, MetricKey.software_quality_reliability_remediation_effort, MetricKey.software_quality_security_remediation_effort, @@ -262,7 +242,6 @@ export const SOFTWARE_QUALITY_RATING_METRICS = [ MetricKey.new_software_quality_maintainability_rating, MetricKey.new_software_quality_security_rating, MetricKey.new_software_quality_reliability_rating, - MetricKey.new_software_quality_security_review_rating, MetricKey.new_software_quality_maintainability_remediation_effort, MetricKey.new_software_quality_reliability_remediation_effort, MetricKey.new_software_quality_security_remediation_effort, diff --git a/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts b/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts index d88e3fcf552..7ce4c1604ed 100644 --- a/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts +++ b/server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts @@ -69,10 +69,8 @@ export enum MetricKey { last_change_on_security_rating = 'last_change_on_security_rating', last_change_on_security_review_rating = 'last_change_on_security_review_rating', last_change_on_software_quality_maintainability_rating = 'last_change_on_software_quality_maintainability_rating', - last_change_on_software_quality_releasability_rating = 'last_change_on_software_quality_releasability_rating', last_change_on_software_quality_reliability_rating = 'last_change_on_software_quality_reliability_rating', last_change_on_software_quality_security_rating = 'last_change_on_software_quality_security_rating', - last_change_on_software_quality_security_review_rating = 'last_change_on_software_quality_security_review_rating', last_commit_date = 'last_commit_date', leak_projects = 'leak_projects', line_coverage = 'line_coverage', @@ -129,9 +127,7 @@ export enum MetricKey { new_security_remediation_effort = 'new_security_remediation_effort', new_software_quality_security_remediation_effort = 'new_software_quality_security_remediation_effort', new_security_review_rating = 'new_security_review_rating', - new_software_quality_security_review_rating = 'new_software_quality_security_review_rating', new_security_review_rating_distribution = 'new_security_review_rating_distribution', - new_software_quality_security_review_rating_distribution = 'new_software_quality_security_review_rating_distribution', new_sqale_debt_ratio = 'new_sqale_debt_ratio', new_software_quality_maintainability_debt_ratio = 'new_software_quality_maintainability_debt_ratio', new_technical_debt = 'new_technical_debt', @@ -152,9 +148,7 @@ export enum MetricKey { quality_profiles = 'quality_profiles', releasability_effort = 'releasability_effort', releasability_rating = 'releasability_rating', - software_quality_releasability_rating = 'software_quality_releasability_rating', releasability_rating_distribution = 'releasability_rating_distribution', - software_quality_releasability_rating_distribution = 'software_quality_releasability_rating_distribution', reliability_issues = 'reliability_issues', reliability_rating = 'reliability_rating', software_quality_reliability_rating = 'software_quality_reliability_rating', @@ -177,11 +171,8 @@ export enum MetricKey { security_remediation_effort = 'security_remediation_effort', software_quality_security_remediation_effort = 'software_quality_security_remediation_effort', security_review_rating = 'security_review_rating', - software_quality_security_review_rating = 'software_quality_security_review_rating', security_review_rating_distribution = 'security_review_rating_distribution', - software_quality_security_review_rating_distribution = 'software_quality_security_review_rating_distribution', security_review_rating_effort = 'security_review_rating_effort', - software_quality_security_review_rating_effort = 'software_quality_security_review_rating_effort', skipped_tests = 'skipped_tests', sonarjava_feedback = 'sonarjava_feedback', sqale_debt_ratio = 'sqale_debt_ratio', diff --git a/server/sonar-web/src/main/js/types/clean-code-taxonomy.ts b/server/sonar-web/src/main/js/types/clean-code-taxonomy.ts index 13bea9be601..d5a80809f20 100644 --- a/server/sonar-web/src/main/js/types/clean-code-taxonomy.ts +++ b/server/sonar-web/src/main/js/types/clean-code-taxonomy.ts @@ -18,9 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ export enum SoftwareImpactSeverity { + Blocker = 'BLOCKER', High = 'HIGH', Medium = 'MEDIUM', Low = 'LOW', + Info = 'INFO', } export enum CleanCodeAttributeCategory { @@ -60,7 +62,9 @@ export interface SoftwareImpact { export interface SoftwareImpactMeasureData { total: number; + [SoftwareImpactSeverity.Blocker]: number; [SoftwareImpactSeverity.High]: number; [SoftwareImpactSeverity.Medium]: number; [SoftwareImpactSeverity.Low]: number; + [SoftwareImpactSeverity.Info]: number; } diff --git a/server/sonar-web/src/main/js/types/jest.d.ts b/server/sonar-web/src/main/js/types/jest.d.ts index 7f43b94e89a..8db287d8628 100644 --- a/server/sonar-web/src/main/js/types/jest.d.ts +++ b/server/sonar-web/src/main/js/types/jest.d.ts @@ -19,6 +19,7 @@ */ declare namespace jest { interface Matchers { + toHaveAPopoverWithContent(content: string): Promise; toHaveATooltipWithContent(content: string): Promise; toHaveNoA11yViolations(): Promise; } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java index 9d6344f96e2..eb678ce714d 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java @@ -21,7 +21,6 @@ package org.sonar.server.measure.index; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -107,11 +106,9 @@ import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; import static org.sonar.server.es.EsUtils.termsToMap; import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE; @@ -175,14 +172,12 @@ public class ProjectMeasuresIndex { NEW_SECURITY_REVIEW_RATING(new RatingMeasureFacet(NEW_SECURITY_REVIEW_RATING_KEY)), //Software quality ratings - SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 4)), - NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 4)), - SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 4)), - NEW_SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 4)), - SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4)), - NEW_SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4)), - SOFTWARE_QUALITY_SECURITY_REVIEW_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4)), - NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4)), + SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)), + NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY)), + SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_RELIABILITY_RATING_KEY)), + NEW_SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY)), + SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_SECURITY_RATING_KEY)), + NEW_SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY)), SECURITY_HOTSPOTS_REVIEWED(new RangeMeasureFacet(SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_REVIEW_RATING_THRESHOLDS)), NEW_SECURITY_HOTSPOTS_REVIEWED(new RangeMeasureFacet(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_REVIEW_RATING_THRESHOLDS)), @@ -588,20 +583,14 @@ public class ProjectMeasuresIndex { private static class RatingMeasureFacet extends MeasureFacet { private RatingMeasureFacet(String metricKey) { - super(metricKey, new MetricRatingFacetBuilder(metricKey, 5)); - } - - private RatingMeasureFacet(String metricKey, int maxRating) { - super(metricKey, new MetricRatingFacetBuilder(metricKey, maxRating)); + super(metricKey, new MetricRatingFacetBuilder(metricKey)); } private static class MetricRatingFacetBuilder implements FacetBuilder { private final String metricKey; - private final int maxRating; - private MetricRatingFacetBuilder(String metricKey, int maxRating) { + private MetricRatingFacetBuilder(String metricKey) { this.metricKey = metricKey; - this.maxRating = maxRating; } @Override @@ -609,20 +598,19 @@ public class ProjectMeasuresIndex { return topAggregationHelper.buildTopAggregation( facet.getName(), facet.getTopAggregationDef(), NO_EXTRA_FILTER, - t -> t.subAggregation(createMeasureRatingFacet(metricKey, maxRating))); + t -> t.subAggregation(createMeasureRatingFacet(metricKey))); } - private static AbstractAggregationBuilder createMeasureRatingFacet(String metricKey, int maxRating) { - List filter = new ArrayList<>(); - for (int i = 1; i <= maxRating; i++) { - filter.add(new KeyedFilter(String.valueOf(i), termQuery(FIELD_MEASURES_MEASURE_VALUE, Double.valueOf(i)))); - } - + private static AbstractAggregationBuilder createMeasureRatingFacet(String metricKey) { return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES) .subAggregation( AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_MEASURE_KEY, metricKey)) - .subAggregation(filters(metricKey, filter.toArray(new KeyedFilter[0]) - ))); + .subAggregation(filters(metricKey, + new KeyedFilter("1", termQuery(FIELD_MEASURES_MEASURE_VALUE, 1D)), + new KeyedFilter("2", termQuery(FIELD_MEASURES_MEASURE_VALUE, 2D)), + new KeyedFilter("3", termQuery(FIELD_MEASURES_MEASURE_VALUE, 3D)), + new KeyedFilter("4", termQuery(FIELD_MEASURES_MEASURE_VALUE, 4D)), + new KeyedFilter("5", termQuery(FIELD_MEASURES_MEASURE_VALUE, 5D))))); } } } diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java index 40a8403f089..ffeffc9647e 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java @@ -52,6 +52,7 @@ import static org.sonar.api.issue.Issue.STATUS_REOPENED; import static org.sonar.api.issue.Issue.STATUS_RESOLVED; 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.Severity.BLOCKER; import static org.sonar.api.rule.Severity.CRITICAL; import static org.sonar.api.rule.Severity.INFO; @@ -115,7 +116,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { newDoc("I4", project.uuid(), file2), newDoc("I5", project.uuid(), file3)); - assertThatFacetHasOnly(IssueQuery.builder(), "files", entry("src/NAME_ABCD", 1L), entry("src/NAME_BCDE", 2L), entry("src/NAME_CDEF", 1L)); + assertThatFacetHasOnly(IssueQuery.builder(), "files", entry("src/NAME_ABCD", 1L), entry("src/NAME_BCDE", 2L), entry("src/NAME_CDEF", + 1L)); } @Test @@ -147,13 +149,15 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { void facet_on_directories_return_100_entries_plus_selected_values() { ComponentDto project = newPrivateProjectDto(); indexIssues( - rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i)), project.uuid()).setDirectoryPath("a" + i)).toArray(IssueDoc[]::new)); + rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i)), project.uuid()).setDirectoryPath("a" + + i)).toArray(IssueDoc[]::new)); IssueDoc issue1 = newDoc(newFileDto(project, newDirectory(project, "path1")), project.uuid()).setDirectoryPath("directory1"); IssueDoc issue2 = newDoc(newFileDto(project, newDirectory(project, "path2")), project.uuid()).setDirectoryPath("directory2"); indexIssues(issue1, issue2); assertThatFacetHasSize(IssueQuery.builder().build(), "directories", 100); - assertThatFacetHasSize(IssueQuery.builder().directories(asList(issue1.directoryPath(), issue2.directoryPath())).build(), "directories", 102); + assertThatFacetHasSize(IssueQuery.builder().directories(asList(issue1.directoryPath(), issue2.directoryPath())).build(), "directories" + , 102); } @Test @@ -275,7 +279,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { ComponentDto file = newFileDto(project); indexIssues( - newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", "insecure-interaction")), + newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", + "insecure-interaction")), newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setSansTop25(singletonList("porous-defenses")), newDoc("I3", project.uuid(), file)); @@ -458,7 +463,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { indexIssues(issue1, issue2); assertThatFacetHasSize(IssueQuery.builder().build(), "assignees", 100); - assertThatFacetHasSize(IssueQuery.builder().assigneeUuids(asList(issue1.assigneeUuid(), issue2.assigneeUuid())).build(), "assignees", 102); + assertThatFacetHasSize(IssueQuery.builder().assigneeUuids(asList(issue1.assigneeUuid(), issue2.assigneeUuid())).build(), "assignees", + 102); } @Test @@ -588,8 +594,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) - .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) + .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -604,8 +610,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) - .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) + .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -622,8 +628,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2011-01-01T00:00:00+0100")) - .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), + .createdAfter(parseDateTime("2011-01-01T00:00:00+0100")) + .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -640,8 +646,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions options = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdAfter(parseDateTime("2014-09-01T00:00:00-0100")) - .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(), + .createdAfter(parseDateTime("2014-09-01T00:00:00-0100")) + .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(), options); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -673,7 +679,7 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { SearchOptions searchOptions = fixtureForCreatedAtFacet(); SearchResponse result = underTest.search(IssueQuery.builder() - .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), + .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(), searchOptions); Map createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt"); assertThat(createdAt).containsOnly( @@ -739,8 +745,8 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { indexIssues( newDoc("I1", project.uuid(), file).setImpacts(Map.of( - MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH, - RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)) + MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH, + RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)) .setTags(singletonList("my-tag")), newDoc("I2", project.uuid(), file).setImpacts(Map.of( MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)), @@ -765,14 +771,15 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { entry("RELIABILITY", 1L), entry("SECURITY", 0L)); - assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities", + assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), + "impactSoftwareQualities", entry("MAINTAINABILITY", 1L), entry("RELIABILITY", 1L), entry("SECURITY", 0L)); assertThatFacetHasOnly(IssueQuery.builder() - .tags(singletonList("my-tag")) - .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities", + .tags(singletonList("my-tag")) + .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities", entry("MAINTAINABILITY", 1L), entry("RELIABILITY", 0L), entry("SECURITY", 0L)); @@ -786,23 +793,26 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { indexIssues( newDoc("I1", project.uuid(), file).setImpacts(Map.of( MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW, - RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))); + RELIABILITY, org.sonar.api.issue.impact.Severity.LOW, + SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER))); assertThatFacetHasOnly(IssueQuery.builder() - .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) - .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), + .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) + .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), "impactSoftwareQualities", entry("MAINTAINABILITY", 1L), entry("RELIABILITY", 1L), entry("SECURITY", 0L)); assertThatFacetHasOnly(IssueQuery.builder() - .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) - .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), + .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) + .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), "impactSeverities", entry("HIGH", 0L), entry("MEDIUM", 0L), - entry("LOW", 1L)); + entry("LOW", 1L), + entry("INFO", 0L), + entry("BLOCKER", 0L)); } @Test @@ -817,14 +827,18 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { newDoc("I2", project.uuid(), file).setImpacts(Map.of( MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)), newDoc("I3", project.uuid(), file).setImpacts(Map.of( - RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)), + RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH, + SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER)), newDoc("I4", project.uuid(), file).setImpacts(Map.of( - MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW))); + MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW, + RELIABILITY, org.sonar.api.issue.impact.Severity.INFO))); assertThatFacetHasOnly(IssueQuery.builder(), "impactSeverities", entry("HIGH", 2L), entry("MEDIUM", 1L), - entry("LOW", 2L)); + entry("LOW", 2L), + entry("BLOCKER", 1L), + entry("INFO", 1L)); } @Test @@ -846,7 +860,9 @@ class IssueIndexFacetsTest extends IssueIndexTestCommon { assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "impactSeverities", entry("HIGH", 1L), entry("MEDIUM", 0L), - entry("LOW", 2L)); + entry("LOW", 2L), + entry("BLOCKER", 0L), + entry("INFO", 0L)); } @Test diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java index 8b21640cbb5..8c2c0ad6af8 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java @@ -85,8 +85,6 @@ class ProjectMeasuresIndexTest { private static final String NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY = "new_software_quality_reliability_rating"; private static final String SOFTWARE_QUALITY_SECURITY_RATING_KEY = "software_quality_security_rating"; private static final String NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY = "new_software_quality_security_rating"; - private static final String SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "software_quality_security_review_rating"; - private static final String NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "new_software_quality_security_review_rating"; private static final String SECURITY_HOTSPOTS_REVIEWED = "security_hotspots_reviewed"; private static final String NEW_SECURITY_HOTSPOTS_REVIEWED = "new_security_hotspots_reviewed"; @@ -127,8 +125,7 @@ class ProjectMeasuresIndexTest { return new String[]{ SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, - SOFTWARE_QUALITY_SECURITY_RATING_KEY, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, - SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY + SOFTWARE_QUALITY_SECURITY_RATING_KEY, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY }; } @@ -1088,7 +1085,10 @@ class ProjectMeasuresIndexTest { newDoc(metricKey, 3d), // 2 docs with rating D newDoc(metricKey, 4d), - newDoc(metricKey, 4d)); + newDoc(metricKey, 4d), + // 1 doc with rating E + newDoc(metricKey, 5d)); + Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets(); @@ -1096,7 +1096,8 @@ class ProjectMeasuresIndexTest { entry("1", 3L), entry("2", 2L), entry("3", 4L), - entry("4", 2L)); + entry("4", 2L), + entry("5", 1L)); } @ParameterizedTest @@ -1160,7 +1161,9 @@ class ProjectMeasuresIndexTest { newDoc(metricKey, 3d, NCLOC, 40000d, COVERAGE, 0d), newDoc(metricKey, 3d, NCLOC, 50000d, COVERAGE, 0d), // docs with rating D - newDoc(metricKey, 4d, NCLOC, 120000d, COVERAGE, 0d)); + newDoc(metricKey, 4d, NCLOC, 120000d, COVERAGE, 0d), + // docs with rating E + newDoc(metricKey, 5d, NCLOC, 120000d, COVERAGE, 0d)); Facets facets = underTest.search(new ProjectMeasuresQuery() .addMetricCriterion(MetricCriterion.create(metricKey, Operator.LT, 3d)) @@ -1172,7 +1175,8 @@ class ProjectMeasuresIndexTest { entry("1", 3L), entry("2", 2L), entry("3", 4L), - entry("4", 1L)); + entry("4", 1L), + entry("5", 1L)); // But facet on ncloc does well take into into filters assertThat(facets.get(NCLOC)).containsExactly( entry("*-1000.0", 3L), @@ -1233,7 +1237,9 @@ class ProjectMeasuresIndexTest { // docs with rating C newDoc(metricKey, 3d), // docs with rating D - newDoc(metricKey, 4d)); + newDoc(metricKey, 4d), + // docs with rating E + newDoc(metricKey, 5d)); userSession.logIn(USER1); Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets(); @@ -1242,7 +1248,8 @@ class ProjectMeasuresIndexTest { entry("1", 3L), entry("2", 2L), entry("3", 0L), - entry("4", 0L)); + entry("4", 0L), + entry("5", 0L)); } @Test diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java index f6c7f9307e5..1ef9f7e82a0 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java @@ -23,9 +23,11 @@ import java.util.Arrays; import org.sonar.api.issue.impact.Severity; public enum ImpactSeverityRestEnum { + INFO(Severity.INFO), LOW(Severity.LOW), MEDIUM(Severity.MEDIUM), - HIGH(Severity.HIGH); + HIGH(Severity.HIGH), + BLOCKER(Severity.BLOCKER); private final Severity severity; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java index 3269fa61cce..f7744ed7dc4 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java @@ -99,11 +99,9 @@ import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_003; @@ -138,7 +136,7 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] software_quality_rating_metric_keys() { return new Object[][]{{SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, - {SOFTWARE_QUALITY_SECURITY_RATING_KEY}, {SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY}}; + {SOFTWARE_QUALITY_SECURITY_RATING_KEY}}; } @DataProvider @@ -157,7 +155,7 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] new_software_quality_rating_metric_keys() { return new Object[][]{{NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, - {NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY}}; + {NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY}}; } @DataProvider @@ -248,11 +246,9 @@ public class SearchProjectsActionIT { "new_software_quality_maintainability_rating", "new_software_quality_reliability_rating", "new_software_quality_security_rating", - "new_software_quality_security_review_rating", "software_quality_maintainability_rating", "software_quality_reliability_rating", - "software_quality_security_rating", - "software_quality_security_review_rating"); + "software_quality_security_rating"); Param asc = def.param("asc"); assertThat(asc.defaultValue()).isEqualTo("true"); @@ -272,11 +268,9 @@ public class SearchProjectsActionIT { "new_software_quality_maintainability_rating", "new_software_quality_reliability_rating", "new_software_quality_security_rating", - "new_software_quality_security_review_rating", "software_quality_maintainability_rating", "software_quality_reliability_rating", - "software_quality_security_rating", - "software_quality_security_review_rating"); + "software_quality_security_rating"); } @Test @@ -1005,6 +999,8 @@ public class SearchProjectsActionIT { insertProject(new Measure(ratingMetric, c -> c.setValue(1d))); insertProject(new Measure(ratingMetric, c -> c.setValue(1d))); insertProject(new Measure(ratingMetric, c -> c.setValue(3d))); + insertProject(new Measure(ratingMetric, c -> c.setValue(5d))); + insertProject(new Measure(ratingMetric, c -> c.setValue(5d))); index(); SearchProjectsWsResponse result = call(request.setFacets(singletonList(ratingMetricKey))); @@ -1018,7 +1014,8 @@ public class SearchProjectsActionIT { tuple("1", 2L), tuple("2", 0L), tuple("3", 1L), - tuple("4", 0L)); + tuple("4", 0L), + tuple("5", 2L)); } @Test @@ -1068,7 +1065,8 @@ public class SearchProjectsActionIT { tuple("1", 2L), tuple("2", 0L), tuple("3", 1L), - tuple("4", 0L)); + tuple("4", 0L), + tuple("5", 0L)); } @Test diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java index 8a581d45c87..0b207e6a128 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java @@ -40,6 +40,7 @@ import org.sonar.db.DbTester; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ProjectData; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.metric.MetricDto; import org.sonar.db.protobuf.DbIssues; @@ -74,6 +75,7 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_RESOLVED; +import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; import static org.sonar.api.measures.CoreMetrics.ANALYSIS_FROM_SONARQUBE_9_4_KEY; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDate; @@ -86,6 +88,7 @@ import static org.sonar.db.rule.RuleTesting.XOO_X1; import static org.sonar.db.rule.RuleTesting.XOO_X2; import static org.sonar.db.rule.RuleTesting.newRule; import static org.sonar.server.tester.UserSessionRule.standalone; +import static org.sonarqube.ws.Common.Impact.newBuilder; @RunWith(DataProviderRunner.class) public class ListActionIT { @@ -196,7 +199,8 @@ public class ListActionIT { .setMessageFormattings(DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build()) .setStatus(STATUS_OPEN) .setResolution(null) - .setSeverity("MAJOR") + .setSeverity("BLOCKER") + .replaceAllImpacts(List.of(new ImpactDto().setSoftwareQuality(MAINTAINABILITY).setSeverity(org.sonar.api.issue.impact.Severity.BLOCKER))) .setAuthorLogin("John") .setAssigneeUuid(simon.getUuid()) .setTags(asList("bug", "owasp")) @@ -217,12 +221,13 @@ public class ListActionIT { .extracting( Issue::getKey, Issue::getRule, Issue::getSeverity, Issue::getComponent, Issue::getResolution, Issue::getStatus, Issue::getMessage, Issue::getMessageFormattingsList, Issue::getEffort, Issue::getAssignee, Issue::getAuthor, Issue::getLine, Issue::getHash, Issue::getTagsList, Issue::getCreationDate, Issue::getUpdateDate, - Issue::getQuickFixAvailable, Issue::getCodeVariantsList) + Issue::getQuickFixAvailable, Issue::getCodeVariantsList, Issue::getImpactsList) .containsExactlyInAnyOrder( - tuple(issue.getKey(), rule.getKey().toString(), Severity.MAJOR, file.getKey(), "", STATUS_OPEN, "the message", + tuple(issue.getKey(), rule.getKey().toString(), Severity.BLOCKER, file.getKey(), "", STATUS_OPEN, "the message", MessageFormattingUtils.dbMessageFormattingListToWs(List.of(MESSAGE_FORMATTING)), "10min", simon.getLogin(), "John", 42, "a227e508d6646b55a086ee11d63b21e9", asList("bug", "owasp"), formatDateTime(issue.getIssueCreationDate()), - formatDateTime(issue.getIssueUpdateDate()), false, List.of("variant1", "variant2"))); + formatDateTime(issue.getIssueUpdateDate()), false, List.of("variant1", "variant2"), + List.of(newBuilder().setSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY).setSeverity(Common.ImpactSeverity.ImpactSeverity_BLOCKER).build()))); assertThat(response.getComponentsList()) .extracting( diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java index 993d5b9abbf..963d8e8771f 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java @@ -28,12 +28,15 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.issue.Issue; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.resources.Qualifiers; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ProjectData; import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.IssueDbTester; import org.sonar.db.issue.IssueDto; import org.sonar.db.project.ProjectDto; @@ -45,6 +48,7 @@ import org.sonar.db.user.UserDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.issue.TaintChecker; import org.sonar.server.issue.ws.pull.PullTaintActionProtobufObjectGenerator; import org.sonar.server.tester.UserSessionRule; @@ -233,6 +237,8 @@ public class PullTaintActionIT { .setManualSeverity(true) .setMessage("message") .setMessageFormattings(DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build()) + .replaceAllImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.BLOCKER), + new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.HIGH))) .setIssueCreationTime(NOW) .setStatus(Issue.STATUS_OPEN) .setLocations(mainLocation.build()) @@ -265,7 +271,7 @@ public class PullTaintActionIT { .containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts() .entrySet() .stream() - .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name()))) + .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), ImpactFormatter.mapImpactSeverity(entry.getValue()))) .collect(toList())); Issues.Location location = taintLite.getMainLocation(); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java index 516107ac4c8..4f45dd7f188 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java @@ -164,18 +164,22 @@ public class SearchActionIT { private final DbClient dbClient = db.getDbClient(); private final DbSession session = db.getSession(); - private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession)); + private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, + new WebAuthorizationTypeSupport(userSession)); private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), null); private final IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession); private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); - private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow)); + private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, + new TransitionService(userSession, issueWorkflow)); private final Languages languages = new Languages(); private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl()); - private final SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new TextRangeResponseFormatter(), userFormatter); + private final SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, + new TextRangeResponseFormatter(), userFormatter); private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = new IssueIndexSyncProgressChecker(dbClient); private final WsActionTester ws = new WsActionTester( - new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, searchResponseFormat, System2.INSTANCE, dbClient)); + new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, + searchResponseFormat, System2.INSTANCE, dbClient)); private final PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); @Before @@ -218,13 +222,16 @@ public class SearchActionIT { assertThat(response.getIssuesList()) .extracting( - Issue::getKey, Issue::getRule, Issue::getSeverity, Issue::getComponent, Issue::getResolution, Issue::getStatus, Issue::getMessage, Issue::getMessageFormattingsList, - Issue::getEffort, Issue::getAssignee, Issue::getAuthor, Issue::getLine, Issue::getHash, Issue::getTagsList, Issue::getCreationDate, Issue::getUpdateDate, + Issue::getKey, Issue::getRule, Issue::getSeverity, Issue::getComponent, Issue::getResolution, Issue::getStatus, Issue::getMessage + , Issue::getMessageFormattingsList, + Issue::getEffort, Issue::getAssignee, Issue::getAuthor, Issue::getLine, Issue::getHash, Issue::getTagsList, + Issue::getCreationDate, Issue::getUpdateDate, Issue::getQuickFixAvailable, Issue::getCodeVariantsList) .containsExactlyInAnyOrder( tuple(issue.getKey(), rule.getKey().toString(), Severity.MAJOR, file.getKey(), RESOLUTION_FIXED, STATUS_RESOLVED, "the message", MessageFormattingUtils.dbMessageFormattingListToWs(List.of(MESSAGE_FORMATTING)), "10min", - simon.getLogin(), "John", 42, "a227e508d6646b55a086ee11d63b21e9", asList("bug", "owasp"), formatDateTime(issue.getIssueCreationDate()), + simon.getLogin(), "John", 42, "a227e508d6646b55a086ee11d63b21e9", asList("bug", "owasp"), + formatDateTime(issue.getIssueCreationDate()), formatDateTime(issue.getIssueUpdateDate()), false, List.of("variant1", "variant2"))); } @@ -250,7 +257,7 @@ public class SearchActionIT { .get(0) .getActions() .getActionsList()) - .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN)); + .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN)); response = ws.newRequest() .setParam(PARAM_ADDITIONAL_FIELDS, "actions") @@ -263,7 +270,7 @@ public class SearchActionIT { .get(0) .getActions() .getActionsList()) - .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY)); + .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY)); } @Test @@ -380,7 +387,8 @@ public class SearchActionIT { SearchWsResponse result = ws.newRequest().executeProtobuf(SearchWsResponse.class); assertThat(result.getIssuesCount()).isOne(); - assertThat(result.getIssues(0).getFlows(0).getLocationsList()).extracting(Common.Location::getComponent, Common.Location::getMsg, Common.Location::getMsgFormattingsList) + assertThat(result.getIssues(0).getFlows(0).getLocationsList()).extracting(Common.Location::getComponent, Common.Location::getMsg, + Common.Location::getMsgFormattingsList) .containsExactlyInAnyOrder( tuple(file.getKey(), "FLOW MESSAGE", List.of()), tuple(anotherFile.getKey(), "ANOTHER FLOW MESSAGE", List.of(Common.MessageFormatting.newBuilder() @@ -503,7 +511,8 @@ public class SearchActionIT { c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")); grantPermissionToAnyone(project.getProjectDto(), ISSUE_ADMIN); indexPermissions(); - ComponentDto file = db.components().insertComponent(newFileDto(project.getMainBranchComponent(), null, "FILE_ID").setKey("FILE_KEY").setLanguage("js")); + ComponentDto file = + db.components().insertComponent(newFileDto(project.getMainBranchComponent(), null, "FILE_ID").setKey("FILE_KEY").setLanguage("js")); IssueDto issue = newIssue(newIssueRule(), project.getMainBranchComponent(), file) .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2") @@ -613,7 +622,8 @@ public class SearchActionIT { c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent(); ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java")); db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_OPEN)); - IssueDto expectedIssue = db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); + IssueDto expectedIssue = db.issues().insertIssue(rule, project, file, + i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE)); db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED)); db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_WONT_FIX)); @@ -728,14 +738,15 @@ public class SearchActionIT { .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH))); IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM)) - .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.LOW))); + .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.INFO))); indexPermissionsAndIssues(); - Map expectedImpacts = Map.of(Common.SoftwareQuality.SECURITY, Common.ImpactSeverity.MEDIUM, - Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.LOW, + Map expectedImpacts = Map.of(Common.SoftwareQuality.SECURITY, + Common.ImpactSeverity.MEDIUM, + Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.ImpactSeverity_INFO, Common.SoftwareQuality.MAINTAINABILITY, Common.ImpactSeverity.HIGH); SearchWsResponse response = ws.newRequest() - .setParam(PARAM_IMPACT_SEVERITIES, org.sonar.api.issue.impact.Severity.LOW.name()) + .setParam(PARAM_IMPACT_SEVERITIES, org.sonar.api.issue.impact.Severity.INFO.name()) .setParam(FACETS, PARAM_IMPACT_SOFTWARE_QUALITIES) .executeProtobuf(SearchWsResponse.class); @@ -777,6 +788,9 @@ public class SearchActionIT { new ImpactDto(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW), new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM), new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)))); + IssueDto issue4 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( + new ImpactDto(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.INFO), + new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER)))); indexPermissionsAndIssues(); SearchWsResponse response = ws.newRequest() @@ -785,7 +799,7 @@ public class SearchActionIT { assertThat(response.getIssuesList()) .extracting(Issue::getKey) - .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey()); + .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey(), issue4.getKey()); Optional first = response.getFacets().getFacetsList() .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES)) @@ -793,9 +807,11 @@ public class SearchActionIT { assertThat(first.get().getValuesList()) .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) .containsExactlyInAnyOrder( + tuple("BLOCKER", 1L), tuple("HIGH", 2L), tuple("MEDIUM", 1L), - tuple("LOW", 1L)); + tuple("LOW", 1L), + tuple("INFO", 1L)); } @Test @@ -809,7 +825,13 @@ public class SearchActionIT { new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)))); db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)))); + IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( + new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER)))); IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( + new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER)))); + IssueDto issue4 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( + new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO)))); + IssueDto issue5 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of( new ImpactDto(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW), new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM), new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)))); @@ -822,7 +844,7 @@ public class SearchActionIT { assertThat(response.getIssuesList()) .extracting(Issue::getKey) - .containsExactlyInAnyOrder(issue1.getKey(), issue3.getKey()); + .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey(), issue4.getKey(), issue5.getKey()); Optional first = response.getFacets().getFacetsList() .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES)) @@ -832,7 +854,9 @@ public class SearchActionIT { .containsExactlyInAnyOrder( tuple("HIGH", 1L), tuple("MEDIUM", 1L), - tuple("LOW", 0L)); + tuple("LOW", 0L), + tuple("INFO", 1L), + tuple("BLOCKER", 2L)); } @Test @@ -878,7 +902,8 @@ public class SearchActionIT { @Test public void issue_on_removed_file() { RuleDto rule = newIssueRule(); - ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setKey("PROJECT_KEY")).getMainBranchComponent(); + ComponentDto project = + db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setKey("PROJECT_KEY")).getMainBranchComponent(); indexPermissions(); ComponentDto removedFile = db.components().insertComponent(newFileDto(project).setUuid("REMOVED_FILE_ID") .setKey("REMOVED_FILE_KEY") @@ -903,7 +928,8 @@ public class SearchActionIT { @Test public void apply_paging_with_one_component() { RuleDto rule = newIssueRule(); - ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setKey("PROJECT_KEY")).getMainBranchComponent(); + ComponentDto project = + db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setKey("PROJECT_KEY")).getMainBranchComponent(); indexPermissions(); ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY")); for (int i = 0; i < SearchOptions.MAX_PAGE_SIZE + 1; i++) { @@ -921,7 +947,8 @@ public class SearchActionIT { public void filter_by_assigned_to_me() { UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com")); - ComponentDto project = db.components().insertPublicProject(c -> c.setUuid("PROJECT_ID").setKey("PROJECT_KEY").setBranchUuid("PROJECT_ID")).getMainBranchComponent(); + ComponentDto project = db.components().insertPublicProject(c -> c.setUuid("PROJECT_ID").setKey("PROJECT_KEY").setBranchUuid( + "PROJECT_ID")).getMainBranchComponent(); indexPermissions(); ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY")); RuleDto rule = newIssueRule(); @@ -969,7 +996,7 @@ public class SearchActionIT { UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID")).getMainBranchComponent(); - SnapshotDto snapshotDto = db.components().insertSnapshot(project, s -> s.setLast(true).setPeriodDate(parseDateTime("2014-09-05T00:00:00+0100").getTime())); + db.components().insertSnapshot(project, s -> s.setLast(true).setPeriodDate(parseDateTime("2014-09-05T00:00:00+0100").getTime())); indexPermissions(); ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY")); @@ -1213,7 +1240,7 @@ public class SearchActionIT { assertThat(ws.newRequest() .setMultiParam("author", singletonList("unknown")) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .isEmpty(); + .isEmpty(); } @Test @@ -1362,7 +1389,8 @@ public class SearchActionIT { assertThat(parse.getAsJsonObject().get("issues").getAsJsonArray()) .extracting(o -> o.getAsJsonObject().get("key").getAsString()) - .containsExactly("82fd47d4-b650-4037-80bc-7b112bd4eac3", "82fd47d4-b650-4037-80bc-7b112bd4eac1", "82fd47d4-b650-4037-80bc-7b112bd4eac2"); + .containsExactly("82fd47d4-b650-4037-80bc-7b112bd4eac3", "82fd47d4-b650-4037-80bc-7b112bd4eac1", "82fd47d4-b650-4037-80bc" + + "-7b112bd4eac2"); } @Test @@ -1372,12 +1400,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-3.2:6.5.3", "owaspAsvs-4.0:12.3.1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1402,13 +1433,15 @@ public class SearchActionIT { public void only_vulnerabilities_are_returned_by_owaspAsvs40_with_level() { ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); ComponentDto file = db.components().insertComponent(newFileDto(project)); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto issueRule1 = db.rules().insertIssueRule(r -> r.setSecurityStandards(Set.of("owaspAsvs-4.0:1.7.2", "owaspAsvs-4.0:12.3.1"))); RuleDto issueRule2 = db.rules().insertIssueRule(r -> r.setSecurityStandards(Set.of("owaspAsvs-4.0:2.2.5"))); RuleDto issueRule3 = db.rules().insertIssueRule(r -> r.setSecurityStandards(Set.of("owaspAsvs-4.0:2.2.5", "owaspAsvs-4.0:12.1.3"))); - IssueDto issueDto1 = db.issues().insertIssue(issueRule1, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule2, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto3 = db.issues().insertIssue(issueRule3, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule1, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto3 = db.issues().insertIssue(issueRule3, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); indexPermissionsAndIssues(); SearchWsResponse result = ws.newRequest() @@ -1453,12 +1486,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-3.2:6.5.3", "pciDss-3.2:10.1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1488,12 +1524,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-3.2:6.5.3", "pciDss-3.2:10.1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); // Rule 2 ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto @@ -1503,8 +1542,10 @@ public class SearchActionIT { hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); // Rule 3 ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto @@ -1514,8 +1555,10 @@ public class SearchActionIT { hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto5 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto6 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto5 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto6 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); indexPermissionsAndIssues(); @@ -1525,7 +1568,8 @@ public class SearchActionIT { assertThat(result.getIssuesList()) .extracting(Issue::getKey) - .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), issueDto6.getKey()); + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), + issueDto6.getKey()); result = ws.newRequest() .setParam("pciDss-3.2", "1") @@ -1541,7 +1585,8 @@ public class SearchActionIT { assertThat(result.getIssuesList()) .extracting(Issue::getKey) - .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), issueDto6.getKey()); + .containsExactlyInAnyOrder(issueDto1.getKey(), issueDto2.getKey(), issueDto3.getKey(), issueDto4.getKey(), issueDto5.getKey(), + issueDto6.getKey()); result = ws.newRequest() .setParam("pciDss-3.2", "4") @@ -1571,12 +1616,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-4.0:6.5.3", "pciDss-4.0:10.1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1606,12 +1654,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "owaspTop10:a1", "pciDss-4.0:6.5.3", "pciDss-4.0:10.1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); // Rule 2 ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto @@ -1621,8 +1672,10 @@ public class SearchActionIT { hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto4 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); // Rule 3 ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto @@ -1632,8 +1685,8 @@ public class SearchActionIT { hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto5 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto6 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); indexPermissionsAndIssues(); @@ -1681,12 +1734,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1706,12 +1762,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1", "owaspTop10-2021:a2")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1731,12 +1790,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1", "owaspTop10-2021:a2")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1754,14 +1816,18 @@ public class SearchActionIT { ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); ComponentDto file = db.components().insertComponent(newFileDto(project)); Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto - .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "stig-ASD_V5R3:V-222402", "stig-ASD_V5R3:V-222403", "stig-ASD_V5R3:V-222404", "ostig-ASD_V5R3:V-222405")) + .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "stig-ASD_V5R3:V-222402", "stig-ASD_V5R3:V-222403", "stig-ASD_V5R3:V" + + "-222404", "ostig-ASD_V5R3:V-222405")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "stig", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "stig", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "stig", "sans-top25-insecure", + "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1790,8 +1856,10 @@ public class SearchActionIT { RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1830,8 +1898,10 @@ public class SearchActionIT { RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1855,8 +1925,10 @@ public class SearchActionIT { RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); IssueDto issueDto3 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(CODE_SMELL)); indexPermissionsAndIssues(); @@ -1900,7 +1972,8 @@ public class SearchActionIT { indexPermissionsAndIssues(); SearchWsResponse result = ws.newRequest() - .setParam("issues", Stream.of(bugIssue, vulnerabilityIssue, codeSmellIssue, hotspot).map(IssueDto::getKey).collect(Collectors.joining(","))) + .setParam("issues", + Stream.of(bugIssue, vulnerabilityIssue, codeSmellIssue, hotspot).map(IssueDto::getKey).collect(Collectors.joining(","))) .executeProtobuf(SearchWsResponse.class); assertThat(result.getIssuesList()) @@ -1915,12 +1988,15 @@ public class SearchActionIT { Consumer ruleConsumer = ruleDefinitionDto -> ruleDefinitionDto .setSecurityStandards(Sets.newHashSet("cwe:20", "cwe:564", "cwe:89", "cwe:943", "owaspTop10:a1")) .setSystemTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); - Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25-insecure", "sql")); + Consumer issueConsumer = issueDto -> issueDto.setTags(Sets.newHashSet("bad-practice", "cwe", "owasp-a1", "sans-top25" + + "-insecure", "sql")); RuleDto hotspotRule = db.rules().insertHotspotRule(ruleConsumer); db.issues().insertHotspot(hotspotRule, project, file, issueConsumer); RuleDto issueRule = db.rules().insertIssueRule(ruleConsumer); - IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); - IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto1 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); + IssueDto issueDto2 = db.issues().insertIssue(issueRule, project, file, issueConsumer, + issueDto -> issueDto.setType(RuleType.VULNERABILITY)); indexPermissions(); indexIssues(); @@ -2014,8 +2090,10 @@ public class SearchActionIT { ComponentDto file = db.components().insertComponent(newFileDto(project)); RuleDto issueRule = db.rules().insertIssueRule(); IssueDto bugIssue = db.issues().insertIssue(issueRule, project, file, i -> i.setType(RuleType.BUG).setSeverity(Severity.MAJOR.name())); - IssueDto vulnerabilityIssue = db.issues().insertIssue(issueRule, project, file, i -> i.setType(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR.name())); - IssueDto codeSmellIssue = db.issues().insertIssue(issueRule, project, file, i -> i.setType(CODE_SMELL).setSeverity(Severity.MAJOR.name())); + IssueDto vulnerabilityIssue = db.issues().insertIssue(issueRule, project, file, + i -> i.setType(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR.name())); + IssueDto codeSmellIssue = db.issues().insertIssue(issueRule, project, file, + i -> i.setType(CODE_SMELL).setSeverity(Severity.MAJOR.name())); RuleDto hotspotRule = db.rules().insertHotspotRule(); db.issues().insertHotspot(hotspotRule, project, file, i -> i.setSeverity(Severity.MAJOR.name())); indexPermissions(); @@ -2152,8 +2230,10 @@ public class SearchActionIT { assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( "additionalFields", "asc", "assigned", "assignees", "author", "components", "branch", "pullRequest", "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", - "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0", - "owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "stig-ASD_V5R3", "casa", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants", + "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4" + + ".0", "owaspAsvs-4.0", + "owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "stig-ASD_V5R3", "casa", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", + "inNewCodePeriod", "codeVariants", "cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities", "issueStatuses", "fixedInPullRequest", "prioritizedRule"); @@ -2163,8 +2243,10 @@ public class SearchActionIT { assertThat(branch.since()).isEqualTo("6.6"); WebService.Param projectUuids = def.param("projects"); - assertThat(projectUuids.description()).isEqualTo("To retrieve issues associated to a specific list of projects (comma-separated list of project keys). " + - "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. If this parameter is set, projectUuids must not be set."); + assertThat(projectUuids.description()).isEqualTo("To retrieve issues associated to a specific list of projects (comma-separated list " + + "of project keys). " + + "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. If this parameter is set, " + + "projectUuids must not be set."); } @Test @@ -2391,9 +2473,9 @@ public class SearchActionIT { private RuleDto newIssueRule(String ruleKey, Consumer consumer) { RuleDto rule = newRule(RuleKey.of("xoo", ruleKey), createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc")) - .setLanguage("xoo") - .setName("Rule name") - .setStatus(RuleStatus.READY); + .setLanguage("xoo") + .setName("Rule name") + .setStatus(RuleStatus.READY); consumer.accept(rule); db.rules().insert(rule); return rule; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java index 0575fd65d47..ebb4b714130 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java @@ -31,12 +31,12 @@ import org.sonar.core.util.SequenceUuidFactory; import org.sonar.core.util.UuidFactory; import org.sonar.db.DbTester; import org.sonar.db.rule.RuleDto; +import org.sonar.server.common.rule.RuleCreator; import org.sonar.server.common.rule.service.RuleService; import org.sonar.server.common.text.MacroInterpreter; import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.common.rule.RuleCreator; import org.sonar.server.rule.RuleDescriptionFormatter; import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.tester.UserSessionRule; @@ -166,7 +166,7 @@ public class CreateActionIT { .setParam("markdownDescription", "Description") .setParam("status", "BETA") .setParam("cleanCodeAttribute", "MODULAR") - .setParam("impacts", "RELIABILITY=HIGH;SECURITY=LOW") + .setParam("impacts", "RELIABILITY=BLOCKER;SECURITY=INFO") .execute().getInput(); String expectedResult = """ @@ -176,7 +176,7 @@ public class CreateActionIT { "repo": "java", "name": "My custom rule", "htmlDesc": "Description", - "severity": "MINOR", + "severity": "INFO", "status": "BETA", "type": "VULNERABILITY", "internalKey": "configKey_S001", @@ -188,11 +188,11 @@ public class CreateActionIT { "impacts": [ { "softwareQuality": "RELIABILITY", - "severity": "HIGH" + "severity": "BLOCKER" }, { "softwareQuality": "SECURITY", - "severity": "LOW" + "severity": "INFO" } ] } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java index cb8219e5d2e..768a7225065 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java @@ -499,7 +499,8 @@ public class SearchActionIT { db.rules().insert( r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH)))); db.rules().insert( - r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.MEDIUM)))); + r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.MEDIUM), + new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.INFO)))); indexRules(); SearchResponse result = ws.newRequest() @@ -507,7 +508,7 @@ public class SearchActionIT { .setParam("impactSoftwareQualities", SoftwareQuality.MAINTAINABILITY.name()) .executeProtobuf(SearchResponse.class); assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(v -> entry(v.getVal(), v.getCount())) - .contains(entry(Severity.HIGH.name(), 1L), entry(Severity.MEDIUM.name(), 0L), entry(Severity.LOW.name(), 0L)); + .contains(entry(Severity.HIGH.name(), 1L), entry(Severity.MEDIUM.name(), 0L), entry(Severity.LOW.name(), 0L), entry(Severity.INFO.name(), 1L)); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java new file mode 100644 index 00000000000..3d018120b07 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 org.sonar.api.issue.impact.Severity; +import org.sonarqube.ws.Common; + +public class ImpactFormatter { + private ImpactFormatter() { + } + + public static Common.ImpactSeverity mapImpactSeverity(Severity severity) { + return switch (severity) { + case BLOCKER -> Common.ImpactSeverity.ImpactSeverity_BLOCKER; + case HIGH -> Common.ImpactSeverity.HIGH; + case MEDIUM -> Common.ImpactSeverity.MEDIUM; + case LOW -> Common.ImpactSeverity.LOW; + case INFO -> Common.ImpactSeverity.ImpactSeverity_INFO; + }; + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index e309eed6fae..49e9e0a6cb0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -49,6 +49,7 @@ import org.sonar.db.rule.RuleDto; import org.sonar.db.user.UserDto; import org.sonar.markdown.Markdown; import org.sonar.server.es.Facets; +import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.index.IssueScope; import org.sonar.server.issue.workflow.Transition; @@ -184,7 +185,7 @@ public class SearchResponseFormat { .stream() .map(entry -> Common.Impact.newBuilder() .setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name())) - .setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name())) + .setSeverity(ImpactFormatter.mapImpactSeverity(entry.getValue())) .build()) .toList()); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java index 18850ad9883..a31082b6693 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java @@ -34,6 +34,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbIssues; import org.sonar.db.rule.RuleDto; +import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.user.UserSession; import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; @@ -108,7 +109,7 @@ public class PullTaintActionProtobufObjectGenerator implements ProtobufObjectGen taintBuilder.addAllImpacts(issueDto.getEffectiveImpacts().entrySet() .stream().map(entry -> Common.Impact.newBuilder() .setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name())) - .setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name())) + .setSeverity(ImpactFormatter.mapImpactSeverity(entry.getValue())) .build()) .toList()); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java index 3f5871bd5fd..b857f21915d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java @@ -50,7 +50,6 @@ import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALIT import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING; import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; @@ -58,11 +57,9 @@ import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RE import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING; import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT; -import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; import static org.sonar.server.measure.Rating.RATING_BY_SEVERITY; import static org.sonar.server.measure.Rating.RATING_BY_SOFTWARE_QUALITY_SEVERITY; import static org.sonar.server.metric.IssueCountMetrics.PRIORITIZED_RULE_ISSUES; -import static org.sonar.server.security.SecurityReviewRating.computeAToDRating; import static org.sonar.server.security.SecurityReviewRating.computePercent; import static org.sonar.server.security.SecurityReviewRating.computeRating; @@ -311,13 +308,13 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact asList(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.NEW_DEVELOPMENT_COST)), new MeasureUpdateFormula(SOFTWARE_QUALITY_MAINTAINABILITY_RATING, false, true, - (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), - (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), asList(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.DEVELOPMENT_COST)), new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING, true, true, - (context, formula) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), - (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, formula) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), asList(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.NEW_DEVELOPMENT_COST)), new MeasureUpdateFormula(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A, false, true, @@ -355,20 +352,6 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact .map(RATING_BY_SOFTWARE_QUALITY_SEVERITY::get) .orElse(Rating.A); context.setValue(rating); - }), - - new MeasureUpdateFormula(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, false, true, - (context, formula) -> context.setValue(computeAToDRating(context.getValue(SECURITY_HOTSPOTS_REVIEWED).orElse(null))), - (context, issues) -> { - Optional percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false)); - context.setValue(computeAToDRating(percent.orElse(null))); - }), - - new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, true, true, - (context, formula) -> context.setValue(computeAToDRating(context.getValue(NEW_SECURITY_HOTSPOTS_REVIEWED).orElse(null))), - (context, issues) -> { - Optional percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true)); - context.setValue(computeAToDRating(percent.orElse(null))); })); private static final Set FORMULA_METRICS = MeasureUpdateFormulaFactory.extractMetrics(FORMULAS); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java index e72cd6362b8..16a11c4b5a5 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java @@ -57,7 +57,6 @@ public class MeasuresWsModule extends Module { SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, - SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, @@ -67,7 +66,6 @@ public class MeasuresWsModule extends Module { SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, - SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java index d4f1ca3f818..5f695661ae8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java @@ -45,6 +45,7 @@ import org.sonar.db.rule.RuleParamDto; import org.sonar.db.user.UserDto; import org.sonar.markdown.Markdown; import org.sonar.server.common.text.MacroInterpreter; +import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.rule.RuleDescriptionFormatter; import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult; import org.sonarqube.ws.Common; @@ -164,7 +165,7 @@ public class RuleMapper { } private static Common.Impact toImpact(ImpactDto impactDto) { - Common.ImpactSeverity severity = Common.ImpactSeverity.valueOf(impactDto.getSeverity().name()); + Common.ImpactSeverity severity = ImpactFormatter.mapImpactSeverity(impactDto.getSeverity()); Common.SoftwareQuality softwareQuality = Common.SoftwareQuality.valueOf(impactDto.getSoftwareQuality().name()); return Common.Impact.newBuilder().setSeverity(severity).setSoftwareQuality(softwareQuality).build(); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java new file mode 100644 index 00000000000..3a801c4e6cb --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 org.junit.jupiter.api.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonarqube.ws.Common; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ImpactFormatterTest { + + @Test + void mapImpactSeverity_shouldReturnExpectedValue() { + assertEquals(Common.ImpactSeverity.ImpactSeverity_BLOCKER, ImpactFormatter.mapImpactSeverity(Severity.BLOCKER)); + assertEquals(Common.ImpactSeverity.HIGH, ImpactFormatter.mapImpactSeverity(Severity.HIGH)); + assertEquals(Common.ImpactSeverity.MEDIUM, ImpactFormatter.mapImpactSeverity(Severity.MEDIUM)); + assertEquals(Common.ImpactSeverity.LOW, ImpactFormatter.mapImpactSeverity(Severity.LOW)); + assertEquals(Common.ImpactSeverity.ImpactSeverity_INFO, ImpactFormatter.mapImpactSeverity(Severity.INFO)); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java index 1eb1e92a377..8d8d29c8a9e 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java @@ -52,7 +52,9 @@ import org.sonar.server.measure.Rating; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.sonar.api.issue.impact.Severity.BLOCKER; import static org.sonar.api.issue.impact.Severity.HIGH; +import static org.sonar.api.issue.impact.Severity.INFO; import static org.sonar.api.issue.impact.Severity.LOW; import static org.sonar.api.issue.impact.Severity.MEDIUM; import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; @@ -167,12 +169,6 @@ class MeasureUpdateFormulaFactoryImplTest { @Test void computeHierarchy_shouldRecomputeSoftwareQualityMetricsCombiningOtherMetrics() { - new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) - .withValue(SECURITY_HOTSPOTS_REVIEWED, 0d) - .expectedRating(Rating.D); - new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) - .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED, 0d) - .expectedRating(Rating.D); new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO) .withValue(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) .withValue(CoreMetrics.DEVELOPMENT_COST, "40") @@ -187,11 +183,26 @@ class MeasureUpdateFormulaFactoryImplTest { .withValue(CoreMetrics.DEVELOPMENT_COST, "40") .expectedRating(Rating.D); + new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .withValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 21d) + .withValue(CoreMetrics.DEVELOPMENT_COST, "40") + .expectedRating(Rating.E); + + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .withValue(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 1d) + .withValue(CoreMetrics.NEW_DEVELOPMENT_COST, 40d) + .expectedRating(Rating.A); + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING) .withValue(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) .withValue(CoreMetrics.NEW_DEVELOPMENT_COST, 40d) .expectedRating(Rating.D); + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .withValue(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 21d) + .withValue(CoreMetrics.NEW_DEVELOPMENT_COST, 40d) + .expectedRating(Rating.E); + new HierarchyTester(SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A) .withValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) .withValue(CoreMetrics.DEVELOPMENT_COST, "40") @@ -264,7 +275,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.BUG).setCount(7), // not bugs newGroup(RuleType.CODE_SMELL).setCount(11)) - .assertThatValueIs(CoreMetrics.BUGS, 3 + 5); + .assertThatValueIs(CoreMetrics.BUGS, 3 + 5); } @Test @@ -277,7 +288,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.CODE_SMELL).setCount(7), // not code smells newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5); + .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5); } @Test @@ -290,7 +301,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.VULNERABILITY).setCount(7), // not vulnerabilities newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5); + .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5); } @Test @@ -303,7 +314,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.SECURITY_HOTSPOT).setCount(7), // not hotspots newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5); } @Test @@ -311,18 +322,15 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.B) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.B); + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.B); withNoIssues() - .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.A) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.A); + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.A); with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(3).setInLeak(true), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true)) - .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.E) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.D); + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.E); } @Test @@ -330,7 +338,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(SECURITY_HOTSPOTS_REVIEWED, 75.0); + .assertThatValueIs(SECURITY_HOTSPOTS_REVIEWED, 75.0); withNoIssues() .assertNoValue(SECURITY_HOTSPOTS_REVIEWED); @@ -341,7 +349,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); withNoIssues() .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0); @@ -352,7 +360,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); withNoIssues() .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0); @@ -380,11 +388,11 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(17), newResolvedGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(19), newResolvedGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.INFO).setCount(21)) - .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13) - .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7) - .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5) - .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0) - .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0); + .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13) + .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7) + .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5) + .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0) + .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0); } @Test @@ -404,8 +412,8 @@ class MeasureUpdateFormulaFactoryImplTest { // exclude unresolved newGroup(RuleType.VULNERABILITY).setCount(17), newGroup(RuleType.BUG).setCount(19)) - .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5) - .assertThatValueIs(CoreMetrics.ACCEPTED_ISSUES, 7 + 11); + .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5) + .assertThatValueIs(CoreMetrics.ACCEPTED_ISSUES, 7 + 11); } @Test @@ -424,9 +432,9 @@ class MeasureUpdateFormulaFactoryImplTest { // exclude security hotspot newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN).setCount(12), newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(13)) - .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5) - .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11) - .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7); + .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5) + .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11) + .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7); } @Test @@ -443,7 +451,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0); } @Test @@ -457,7 +465,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.BUG).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0); } @Test @@ -471,19 +479,21 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0); } private static Stream maintainabilityMetrics() { return Stream.of( arguments(TECHNICAL_DEBT, SQALE_DEBT_RATIO, SQALE_RATING), - arguments(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, + arguments(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING)); } @ParameterizedTest @MethodSource("maintainabilityMetrics") - void test_sqale_debt_ratio_and_sqale_rating(Metric maintainabilityRemediationEffortMetric, Metric maintainabilityDebtRatioMetric, Metric maintainabilityRatingMetric) { + void test_sqale_debt_ratio_and_sqale_rating(Metric maintainabilityRemediationEffortMetric, Metric maintainabilityDebtRatioMetric, + Metric maintainabilityRatingMetric) { withNoIssues() .assertThatValueIs(maintainabilityDebtRatioMetric, 0) .assertThatValueIs(maintainabilityRatingMetric, Rating.A); @@ -519,8 +529,8 @@ class MeasureUpdateFormulaFactoryImplTest { .andText(CoreMetrics.DEVELOPMENT_COST, "10") .assertThatValueIs(maintainabilityDebtRatioMetric, 200.0); switch (maintainabilityRatingMetric.key()) { - case SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY -> verifier.assertThatValueIs(maintainabilityRatingMetric, Rating.D); - case SQALE_RATING_KEY -> verifier.assertThatValueIs(maintainabilityRatingMetric, Rating.E); + case SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, SQALE_RATING_KEY -> + verifier.assertThatValueIs(maintainabilityRatingMetric, Rating.E); default -> throw new IllegalArgumentException("Unexpected metric: " + maintainabilityRatingMetric.key()); } @@ -619,14 +629,14 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(5), // excluded, not a bug newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3)) - // highest severity of bugs is CRITICAL --> D - .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D); + // highest severity of bugs is CRITICAL --> D + .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D); with( newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3), newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5)) - // no bugs --> A - .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A); + // no bugs --> A + .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A); } @Test @@ -634,16 +644,28 @@ class MeasureUpdateFormulaFactoryImplTest { withNoIssues() .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); + with( + newImpactGroup(RELIABILITY, BLOCKER, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.E); + + with( + newImpactGroup(RELIABILITY, HIGH, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.D); + with( newImpactGroup(MAINTAINABILITY, HIGH, 1), newImpactGroup(RELIABILITY, MEDIUM, 1), newImpactGroup(RELIABILITY, LOW, 1), newImpactGroup(SECURITY, MEDIUM, 1)) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); with( newImpactGroup(RELIABILITY, LOW, 1)) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); + + with( + newImpactGroup(RELIABILITY, INFO, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); } @Test @@ -651,18 +673,32 @@ class MeasureUpdateFormulaFactoryImplTest { withNoIssues() .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); + with( + newImpactGroup(RELIABILITY, BLOCKER, 1, true)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.E); + + with( + newImpactGroup(RELIABILITY, HIGH, 1, true), + newImpactGroup(RELIABILITY, BLOCKER, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.D); + with( newImpactGroup(MAINTAINABILITY, HIGH, 1, true), newImpactGroup(RELIABILITY, MEDIUM, 1, true), newImpactGroup(RELIABILITY, LOW, 1, true), newImpactGroup(RELIABILITY, HIGH, 1, false), newImpactGroup(SECURITY, HIGH, 1, true)) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); with( newImpactGroup(RELIABILITY, LOW, 1, true), newImpactGroup(RELIABILITY, MEDIUM, 1, false)) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); + + with( + newImpactGroup(RELIABILITY, INFO, 1, true), + newImpactGroup(RELIABILITY, MEDIUM, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); } @Test @@ -675,14 +711,14 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(5), // excluded, not a vulnerability newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3)) - // highest severity of vulnerabilities is CRITICAL --> D - .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D); + // highest severity of vulnerabilities is CRITICAL --> D + .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D); with( newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3), newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5)) - // no vulnerabilities --> A - .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A); + // no vulnerabilities --> A + .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A); } @Test @@ -690,16 +726,28 @@ class MeasureUpdateFormulaFactoryImplTest { withNoIssues() .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); + with( + newImpactGroup(SECURITY, BLOCKER, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.E); + + with( + newImpactGroup(SECURITY, HIGH, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.D); + with( newImpactGroup(MAINTAINABILITY, HIGH, 1), newImpactGroup(SECURITY, MEDIUM, 1), newImpactGroup(SECURITY, LOW, 1), newImpactGroup(RELIABILITY, MEDIUM, 1)) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); with( newImpactGroup(SECURITY, LOW, 1)) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); + + with( + newImpactGroup(SECURITY, INFO, 2)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); } @Test @@ -707,18 +755,32 @@ class MeasureUpdateFormulaFactoryImplTest { withNoIssues() .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); + with( + newImpactGroup(SECURITY, BLOCKER, 1, true)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.E); + + with( + newImpactGroup(SECURITY, HIGH, 1, true), + newImpactGroup(SECURITY, BLOCKER, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.D); + with( newImpactGroup(MAINTAINABILITY, HIGH, 1, true), newImpactGroup(SECURITY, MEDIUM, 1, true), newImpactGroup(SECURITY, LOW, 1, true), newImpactGroup(SECURITY, HIGH, 1, false), newImpactGroup(RELIABILITY, HIGH, 1, true)) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); with( newImpactGroup(SECURITY, LOW, 1, true), newImpactGroup(SECURITY, MEDIUM, 1, false)) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); + + with( + newImpactGroup(SECURITY, INFO, 1, true), + newImpactGroup(SECURITY, MEDIUM, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); } @Test @@ -732,7 +794,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not bugs newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(9), newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7); } @@ -747,7 +809,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not code smells newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7); } @Test @@ -761,7 +823,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not vulnerabilities newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7); } @Test @@ -775,7 +837,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not hotspots newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7); } @Test @@ -790,7 +852,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setInLeak(false).setCount(11), newGroup(RuleType.CODE_SMELL).setInLeak(false).setCount(13), newGroup(RuleType.VULNERABILITY).setInLeak(false).setCount(17)) - .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9); + .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9); } @Test @@ -807,7 +869,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7); } @Test @@ -824,7 +886,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7); } @Test @@ -841,7 +903,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7); } @Test @@ -858,7 +920,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7); } @Test @@ -875,7 +937,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7); } @Test @@ -891,7 +953,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(5), newGroup(RuleType.BUG).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(50)) - .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 4 + 40); + .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 4 + 40); } @Test @@ -907,7 +969,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0); } @Test @@ -922,7 +984,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.BUG).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0); } @Test @@ -937,7 +999,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0); } @Test @@ -953,8 +1015,8 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true)) - // highest severity of bugs on leak period is minor -> B - .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B); + // highest severity of bugs on leak period is minor -> B + .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B); } @Test @@ -970,8 +1032,8 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true)) - // highest severity of bugs on leak period is minor -> B - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B); + // highest severity of bugs on leak period is minor -> B + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B); } @Test @@ -981,20 +1043,17 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false)) - .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.B) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.B); + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.B); withNoIssues() - .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.A) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.A); + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.A); with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(3).setInLeak(true), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false)) - .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.E) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.D); + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.E); } @Test @@ -1004,7 +1063,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0); withNoIssues() .assertNoLeakValue(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED); @@ -1017,7 +1076,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); withNoIssues() .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0); @@ -1030,7 +1089,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); withNoIssues() .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0); @@ -1039,13 +1098,15 @@ class MeasureUpdateFormulaFactoryImplTest { private static Stream newMaintainabilityMetrics() { return Stream.of( arguments(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_SQALE_DEBT_RATIO, CoreMetrics.NEW_MAINTAINABILITY_RATING), - arguments(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, + arguments(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING)); } @ParameterizedTest @MethodSource("newMaintainabilityMetrics") - void test_new_sqale_debt_ratio_and_new_maintainability_rating(Metric newMaintainabilityRemediationEffortMetric, Metric newMaintainabilityDebtRatioMetric, + void test_new_sqale_debt_ratio_and_new_maintainability_rating(Metric newMaintainabilityRemediationEffortMetric, + Metric newMaintainabilityDebtRatioMetric, Metric newMaintainabilityRatingMetric) { withNoIssues() .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) @@ -1082,8 +1143,8 @@ class MeasureUpdateFormulaFactoryImplTest { .and(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0D) .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 200.0); switch (newMaintainabilityRatingMetric.key()) { - case NEW_MAINTAINABILITY_RATING_KEY -> verifier.assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.E); - case SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY -> verifier.assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.D); + case NEW_MAINTAINABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY -> + verifier.assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.E); default -> throw new IllegalArgumentException("Unexpected metric: " + newMaintainabilityRatingMetric.key()); } @@ -1135,7 +1196,7 @@ class MeasureUpdateFormulaFactoryImplTest { newImpactGroup(SECURITY, LOW, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 6), newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE, 7), newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8)) - .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 8); + .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 8); } @Test @@ -1157,12 +1218,12 @@ class MeasureUpdateFormulaFactoryImplTest { newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 4, 1d, false), newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false), newImpactGroup(MAINTAINABILITY, MEDIUM, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false)) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 1d + 2d) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 1d + 2d) - .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 1d) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 2d) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 2d) - .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 0d); + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 1d + 2d) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 1d + 2d) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 1d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 2d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 2d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 0d); } @Test @@ -1186,9 +1247,9 @@ class MeasureUpdateFormulaFactoryImplTest { newImpactGroup(MAINTAINABILITY, MEDIUM, 10), newImpactGroup(MAINTAINABILITY, LOW, 11), newImpactGroup(SECURITY, HIGH, 3)) - .assertThatJsonValueIs(CoreMetrics.RELIABILITY_ISSUES, impactMeasureToJson(8, 3, 4, 1)) - .assertThatJsonValueIs(CoreMetrics.MAINTAINABILITY_ISSUES, impactMeasureToJson(21, 0, 10, 11)) - .assertThatJsonValueIs(CoreMetrics.SECURITY_ISSUES, impactMeasureToJson(3, 3, 0, 0)); + .assertThatJsonValueIs(CoreMetrics.RELIABILITY_ISSUES, impactMeasureToJson(8, 3, 4, 1)) + .assertThatJsonValueIs(CoreMetrics.MAINTAINABILITY_ISSUES, impactMeasureToJson(21, 0, 10, 11)) + .assertThatJsonValueIs(CoreMetrics.SECURITY_ISSUES, impactMeasureToJson(3, 3, 0, 0)); } @Test @@ -1347,19 +1408,23 @@ class MeasureUpdateFormulaFactoryImplTest { return newImpactGroup(softwareQuality, severity, status, resolution, count, 0, false); } - private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count) { + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, + long count) { return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, 0, false); } - private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, boolean inLeak) { + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, + long count, boolean inLeak) { return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, 0, inLeak); } - private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, double effort) { + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, + long count, double effort) { return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, effort, false); } - private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, double effort, boolean inLeak) { + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, + long count, double effort, boolean inLeak) { return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, effort, inLeak); } @@ -1420,7 +1485,7 @@ class MeasureUpdateFormulaFactoryImplTest { @Override public DebtRatingGrid getDebtRatingGrid() { - return new DebtRatingGrid(new double[] {0.05, 0.1, 0.2, 0.5}); + return new DebtRatingGrid(new double[]{0.05, 0.1, 0.2, 0.5}); } @Override @@ -1491,7 +1556,8 @@ class MeasureUpdateFormulaFactoryImplTest { return this; } - public HierarchyTester withChildrenHotspotsCounts(long childrenHotspotsReviewed, long childrenNewHotspotsReviewed, long childrenHotspotsToReview, + public HierarchyTester withChildrenHotspotsCounts(long childrenHotspotsReviewed, long childrenNewHotspotsReviewed, + long childrenHotspotsToReview, long childrenNewHotspotsToReview) { this.initialValues.childrenHotspotsReviewed = childrenHotspotsReviewed; this.initialValues.childrenNewHotspotsReviewed = childrenNewHotspotsReviewed; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java index 51d90e14b61..1463035e7f1 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java @@ -52,10 +52,10 @@ public class MeasuresWsModuleTest { public void getNewMetricsInSonarQube107_shouldReturnExactString() { String actual = MeasuresWsModule.getNewMetricsInSonarQube107(); assertThat(actual).isEqualTo("'software_quality_maintainability_debt_ratio', 'software_quality_maintainability_rating', 'software_quality_reliability_rating', " + - "'software_quality_security_rating', 'software_quality_security_review_rating', 'software_quality_maintainability_remediation_effort', " + + "'software_quality_security_rating', 'software_quality_maintainability_remediation_effort', " + "'software_quality_reliability_remediation_effort', 'software_quality_security_remediation_effort', 'effort_to_reach_software_quality_maintainability_rating_a', " + "'new_software_quality_maintainability_debt_ratio', 'new_software_quality_maintainability_rating', 'new_software_quality_reliability_rating', " + - "'new_software_quality_security_rating', 'new_software_quality_security_review_rating', 'new_software_quality_maintainability_remediation_effort'," + + "'new_software_quality_security_rating', 'new_software_quality_maintainability_remediation_effort'," + " 'new_software_quality_reliability_remediation_effort', 'new_software_quality_security_remediation_effort'"); } } diff --git a/sonar-core/src/main/java/org/sonar/core/metric/SoftwareQualitiesMetrics.java b/sonar-core/src/main/java/org/sonar/core/metric/SoftwareQualitiesMetrics.java index 5738de6bdf6..ddac90bfbc4 100644 --- a/sonar-core/src/main/java/org/sonar/core/metric/SoftwareQualitiesMetrics.java +++ b/sonar-core/src/main/java/org/sonar/core/metric/SoftwareQualitiesMetrics.java @@ -26,7 +26,6 @@ import org.sonar.api.measures.Metrics; import static org.sonar.api.measures.CoreMetrics.DOMAIN_MAINTAINABILITY; import static org.sonar.api.measures.CoreMetrics.DOMAIN_RELIABILITY; import static org.sonar.api.measures.CoreMetrics.DOMAIN_SECURITY; -import static org.sonar.api.measures.CoreMetrics.DOMAIN_SECURITY_REVIEW; public class SoftwareQualitiesMetrics implements Metrics { @@ -39,7 +38,7 @@ public class SoftwareQualitiesMetrics implements Metrics { .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); public static final String NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY = "new_software_quality_maintainability_rating"; @@ -54,7 +53,7 @@ public class SoftwareQualitiesMetrics implements Metrics { .setOptimizedBestValue(true) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); public static final String SOFTWARE_QUALITY_RELIABILITY_RATING_KEY = "software_quality_reliability_rating"; @@ -66,7 +65,7 @@ public class SoftwareQualitiesMetrics implements Metrics { .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); public static final String NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY = "new_software_quality_reliability_rating"; @@ -81,7 +80,7 @@ public class SoftwareQualitiesMetrics implements Metrics { .setOptimizedBestValue(true) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); public static final String SOFTWARE_QUALITY_SECURITY_RATING_KEY = "software_quality_security_rating"; @@ -93,7 +92,7 @@ public class SoftwareQualitiesMetrics implements Metrics { .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); public static final String NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY = "new_software_quality_security_rating"; @@ -107,36 +106,9 @@ public class SoftwareQualitiesMetrics implements Metrics { .setOptimizedBestValue(true) .setQualitative(true) .setBestValue(1.0) - .setWorstValue(4.0) + .setWorstValue(5.0) .create(); - public static final String SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "software_quality_security_review_rating"; - - public static final Metric SOFTWARE_QUALITY_SECURITY_REVIEW_RATING = - new Metric.Builder(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, "Software Quality Security Review Rating", Metric.ValueType.RATING) - .setDescription("Software quality security review rating") - .setDomain(DOMAIN_SECURITY_REVIEW) - .setDirection(Metric.DIRECTION_WORST) - .setQualitative(true) - .setBestValue(1.0) - .setWorstValue(4.0) - .create(); - - public static final String NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "new_software_quality_security_review_rating"; - - public static final Metric NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING = - new Metric.Builder(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, "Software Quality Security Review Rating on New Code", - Metric.ValueType.RATING) - .setDescription("Software quality security review rating on new code") - .setDomain(DOMAIN_SECURITY_REVIEW) - .setDirection(Metric.DIRECTION_WORST) - .setDeleteHistoricalData(true) - .setOptimizedBestValue(true) - .setQualitative(true) - .setBestValue(1.0) - .setWorstValue(4.0) - .create(); - public static final String EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY = "effort_to_reach_software_quality_maintainability_rating_a"; @@ -271,8 +243,6 @@ public class SoftwareQualitiesMetrics implements Metrics { NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, SOFTWARE_QUALITY_SECURITY_RATING, NEW_SOFTWARE_QUALITY_SECURITY_RATING, - SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, - NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A, SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, diff --git a/sonar-core/src/main/java/org/sonar/core/util/ProtobufJsonFormat.java b/sonar-core/src/main/java/org/sonar/core/util/ProtobufJsonFormat.java index e849eba4b3c..522f4dd84f5 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/ProtobufJsonFormat.java +++ b/sonar-core/src/main/java/org/sonar/core/util/ProtobufJsonFormat.java @@ -222,7 +222,8 @@ public class ProtobufJsonFormat { writer.value((String) value); break; case ENUM: - writer.value(((Descriptors.EnumValueDescriptor) value).getName()); + String enumValue = formatEnumValue(fieldDescriptor, (Descriptors.EnumValueDescriptor) value); + writer.value(enumValue); break; case MESSAGE: writeMessageValue((Message) value, writer); @@ -232,6 +233,20 @@ public class ProtobufJsonFormat { } } + /** + * As a limitation from protobuf, there can't be the same enum value defined twice in proto, even if they belong to different enums. + * To remove this constraint, we let the possibility to have the enum name as the prefix of the enum value to make it unique. + * The class will make sure to remove it when converted to JSON. + * @see https://github.com/protocolbuffers/protobuf/issues/5425 + */ + private static String formatEnumValue(Descriptors.FieldDescriptor fieldDescriptor, Descriptors.EnumValueDescriptor value) { + String enumValue = value.getName(); + if (enumValue.startsWith(fieldDescriptor.getEnumType().getName())) { + enumValue = enumValue.substring(fieldDescriptor.getEnumType().getName().length() + 1); + } + return enumValue; + } + private static void writeMessageValue(Message message, JsonWriter writer) { MessageType messageType = MessageType.of(message); if (messageType.doesWrapRepeated) { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 890d4875c16..7604ab16d9f 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1244,8 +1244,6 @@ issues.facet.createdAt.last_year=Last year issues.facet.createdAt.bar_description={0} issues from {1} to {2} issues.facet.authors=Author issues.facet.impactSeverities=Severity -issues.facet.impactSeverities.help.line1=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity. -issues.facet.impactSeverities.help.line2=There are only three levels: high, medium, and low. issues.facet.issues=Issue Key issues.facet.mode=Display Mode issues.facet.mode.count=Issues @@ -1328,24 +1326,38 @@ projects.limited_set_of_projects=Displayed project set limited to the top {0} pr projects.facets.quality_gate=Quality Gate projects.facets.quality_gate.warning_help=Warning status is deprecated. This filter will disappear when no Warning Quality Gate remains. projects.facets.rating_x={0} rating -projects.facets.rating_option.reliability.1=0 issues +projects.facets.rating_option.reliability.legacy.1=≥ 0 info issues +projects.facets.rating_option.reliability.legacy.2=≥ 1 minor issue +projects.facets.rating_option.reliability.legacy.3=≥ 1 major issue +projects.facets.rating_option.reliability.legacy.4=≥ 1 critical issue +projects.facets.rating_option.reliability.legacy.5=≥ 1 blocker issue +projects.facets.rating_option.reliability.1=≥ 0 info issues projects.facets.rating_option.reliability.2=≥ 1 low issue projects.facets.rating_option.reliability.3=≥ 1 medium issue projects.facets.rating_option.reliability.4=≥ 1 high issue -projects.facets.rating_option.security.1=0 issues +projects.facets.rating_option.reliability.5=≥ 1 blocker issue +projects.facets.rating_option.security.legacy.1=≥ 0 info issues +projects.facets.rating_option.security.legacy.2=≥ 1 minor issue +projects.facets.rating_option.security.legacy.3=≥ 1 major issue +projects.facets.rating_option.security.legacy.4=≥ 1 critical issue +projects.facets.rating_option.security.legacy.5=≥ 1 blocker issue +projects.facets.rating_option.security.1=≥ 0 info issues projects.facets.rating_option.security.2=≥ 1 low issue projects.facets.rating_option.security.3=≥ 1 medium issue projects.facets.rating_option.security.4=≥ 1 high issue +projects.facets.rating_option.security.5=≥ 1 blocker issue projects.facets.rating_option.maintainability.1=≤ 5% to 0% projects.facets.rating_option.maintainability.2=≥ 5% to <10% projects.facets.rating_option.maintainability.3=≥ 10% to <20% -projects.facets.rating_option.maintainability.4=≥ 20% -projects.facets.rating_option.security_review.1== 100% -projects.facets.rating_option.security_review.2=≥ 70% to <100% +projects.facets.rating_option.maintainability.4=≥ 20% to <50% +projects.facets.rating_option.maintainability.5=≥ 50% +projects.facets.rating_option.security_review.1=≥ 80% +projects.facets.rating_option.security_review.2=≥ 70% to <80% projects.facets.rating_option.security_review.3=≥ 50% to <70% -projects.facets.rating_option.security_review.4=< 50% +projects.facets.rating_option.security_review.4=≥ 30% to <50% +projects.facets.rating_option.security_review.5=< 30% projects.facets.security_review.description=The percentage of reviewed (fixed or safe) security hotspots -projects.facets.maintainability.description=Ratio of the size of the project to the estimated time needed to fix all outstanding maintainability issues +projects.facets.maintainability.description=Ratio of the estimated time needed to fix all outstanding maintainability issues to the size of the project projects.facets.languages=Languages projects.facets.search.languages=Search for languages projects.facets.new_lines=New Lines @@ -2938,12 +2950,20 @@ severity.INFO=Info severity.INFO.description=Neither a bug nor a quality flaw. Just a finding. # New severities -severity.HIGH=High -severity.HIGH.description=Must be fixed immediately. -severity.MEDIUM=Medium -severity.MEDIUM.description=High potential for significant to moderate impact. -severity.LOW=Low -severity.LOW.description=Potential for moderate to minor impact. +severity_impact.title=Severity of impact +severity_impact.levels=Severity levels +severity_impact.BLOCKER=Blocker +severity_impact.BLOCKER.description=Must be fixed immediately. +severity_impact.HIGH=High +severity_impact.HIGH.description=Must be reviewed immediately and fixed soon. +severity_impact.MEDIUM=Medium +severity_impact.MEDIUM.description=High potential for significant to moderate impact. +severity_impact.LOW=Low +severity_impact.LOW.description=Potential for moderate to minor impact. +severity_impact.INFO=Info +severity_impact.INFO.description=Neither a bug nor a quality flaw. Just a finding. +severity_impact.help.line1=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity. +severity_impact.help.line2=There are five levels of severity: blocker, high, medium, low and info. #------------------------------------------------------------------------------ @@ -3309,9 +3329,6 @@ metric.new_software_quality_security_remediation_effort.extra_short_name=Remedia metric.new_security_review_rating.description=Security Review Rating on New Code metric.new_security_review_rating.name=Security Review Rating on New Code metric.new_security_review_rating.extra_short_name=Rating -metric.new_software_quality_security_review_rating.description=Security Review Rating on New Code -metric.new_software_quality_security_review_rating.name=Security Review Rating on New Code -metric.new_software_quality_security_review_rating.extra_short_name=Rating metric.new_sqale_debt_ratio.description=Technical Debt Ratio of new/changed code. metric.new_sqale_debt_ratio.name=Technical Debt Ratio on New Code metric.new_sqale_debt_ratio.short_name=Debt Ratio on new code @@ -3413,11 +3430,11 @@ metric.software_quality_releasability_rating.name=Releasability Rating metric.software_quality_reliability_rating.description=Reliability rating metric.software_quality_reliability_rating.name=Reliability Rating metric.software_quality_reliability_rating.extra_short_name=Rating -metric.software_quality_reliability_rating.tooltip.A=Reliability rating is A when there are no reliability issues. +metric.software_quality_reliability_rating.tooltip.A=Reliability rating is A when there are no reliability issues above info severity. metric.software_quality_reliability_rating.tooltip.B=Reliability rating is B when there is at least one low reliability issue. metric.software_quality_reliability_rating.tooltip.C=Reliability rating is C when there is at least one medium reliability issue. metric.software_quality_reliability_rating.tooltip.D=Reliability rating is D when there is at least one high reliability issue. -metric.software_quality_reliability_rating.tooltip.E=Reliability rating is E when there is at least one high reliability issue. +metric.software_quality_reliability_rating.tooltip.E=Reliability rating is E when there is at least one blocker reliability issue. metric.reliability_remediation_effort.description=Reliability Remediation Effort metric.reliability_remediation_effort.name=Reliability Remediation Effort metric.reliability_remediation_effort.extra_short_name=Remediation Effort @@ -3452,11 +3469,11 @@ metric.security_rating.tooltip.E=Security rating is E when there is at least one metric.software_quality_security_rating.description=Security rating metric.software_quality_security_rating.name=Security Rating metric.software_quality_security_rating.extra_short_name=Rating -metric.software_quality_security_rating.tooltip.A=Security rating is A when there are no security issues. +metric.software_quality_security_rating.tooltip.A=Security rating is A when there are no security issues above info severity. metric.software_quality_security_rating.tooltip.B=Security rating is B when there is at least one low security issue. metric.software_quality_security_rating.tooltip.C=Security rating is C when there is at least one medium security issue. metric.software_quality_security_rating.tooltip.D=Security rating is D when there is at least one high security issue. -metric.software_quality_security_rating.tooltip.E=Security rating is E when there is at least one high security issue. +metric.software_quality_security_rating.tooltip.E=Security rating is E when there is at least one blocker security issue. metric.security_remediation_effort.description=Security remediation effort metric.security_remediation_effort.name=Security Remediation Effort metric.security_remediation_effort.extra_short_name=Remediation Effort @@ -3466,19 +3483,11 @@ metric.software_quality_security_remediation_effort.extra_short_name=Remediation metric.security_review_rating.description=Security Review Rating metric.security_review_rating.name=Security Review Rating metric.security_review_rating.extra_short_name=Rating -metric.software_quality_security_review_rating.description=Security Review Rating -metric.software_quality_security_review_rating.name=Security Review Rating -metric.software_quality_security_review_rating.extra_short_name=Rating metric.security_review_rating.tooltip.A=Security Review rating is A when at least 80% of Security Hotspots are reviewed. metric.security_review_rating.tooltip.B=Security Review rating is B when less than 80% of Security Hotspots are reviewed. metric.security_review_rating.tooltip.C=Security Review rating is C when less than 70% of Security Hotspots are reviewed. metric.security_review_rating.tooltip.D=Security Review rating is D when less than 50% of Security Hotspots are reviewed. metric.security_review_rating.tooltip.E=Security Review rating is E when less than 30% of Security Hotspots are reviewed. -metric.software_quality_security_review_rating.tooltip.A=Security Review rating is A when all Security Hotspots are reviewed. -metric.software_quality_security_review_rating.tooltip.B=Security Review rating is B when less than 100% of Security Hotspots are reviewed. -metric.software_quality_security_review_rating.tooltip.C=Security Review rating is C when less than 70% of Security Hotspots are reviewed. -metric.software_quality_security_review_rating.tooltip.D=Security Review rating is D when less than 50% of Security Hotspots are reviewed. -metric.software_quality_security_review_rating.tooltip.E=Security Review rating is E when less than 30% of Security Hotspots are reviewed. metric.skipped_tests.description=Number of skipped unit tests metric.skipped_tests.name=Skipped Unit Tests metric.skipped_tests.short_name=Skipped @@ -4221,7 +4230,7 @@ overview.measures.software_impact.severity.LOW=L overview.measures.software_impact.severity.LOW.tooltip=Low Impact overview.measures.software_impact.severity.LOW.improve_tooltip=low overview.measures.software_impact.improve_rating_tooltip=The {softwareQuality} rating is {ratingLabel} when there is at least one issue with {severity} impact on the {_softwareQuality} of your software. -overview.measures.software_impact.improve_rating_tooltip.A=The {softwareQuality} rating is {ratingLabel} when there are no issues with impact on the {_softwareQuality} of your software. +overview.measures.software_impact.improve_rating_tooltip.A=The {softwareQuality} rating is {ratingLabel} when there are no issues above info severity with impact on the {_softwareQuality} of your software. overview.measures.software_impact.improve_rating_tooltip.MAINTAINABILITY=The Maintainability rating is {ratingLabel} if the code has a relatively higher level of technical debt when compared to the size of the codebase. overview.measures.software_impact.improve_rating_tooltip.MAINTAINABILITY.A=The Maintainability rating is A if the code has a relatively lower level of technical debt when compared to the size of the codebase. diff --git a/sonar-core/src/test/java/org/sonar/core/metric/SoftwareQualitiesMetricsTest.java b/sonar-core/src/test/java/org/sonar/core/metric/SoftwareQualitiesMetricsTest.java index 1f46975d3c4..a230b33a6d5 100644 --- a/sonar-core/src/test/java/org/sonar/core/metric/SoftwareQualitiesMetricsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/metric/SoftwareQualitiesMetricsTest.java @@ -34,8 +34,6 @@ class SoftwareQualitiesMetricsTest { SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, - SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, - SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, diff --git a/sonar-core/src/test/java/org/sonar/core/util/ProtobufJsonFormatTest.java b/sonar-core/src/test/java/org/sonar/core/util/ProtobufJsonFormatTest.java index 6dd4375a170..d5721ebdcb5 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/ProtobufJsonFormatTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/ProtobufJsonFormatTest.java @@ -58,6 +58,21 @@ public class ProtobufJsonFormatTest { "{\"stringField\":\"foo\",\"intField\":10,\"longField\":100,\"doubleField\":3.14,\"booleanField\":true,\"enumField\":\"GREEN\"}"); } + @Test + public void toJson_whenPrefixedEnum_shouldConvertToExpectedEnumValue() { + PrimitiveTypeMsg protobuf = PrimitiveTypeMsg.newBuilder() + .setStringField("foo") + .setIntField(10) + .setLongField(100L) + .setDoubleField(3.14) + .setBooleanField(true) + .setEnumField(org.sonar.core.test.Test.FakeEnum.FakeEnum_YELLOW) + .build(); + + assertThat(toJson(protobuf)).isEqualTo( + "{\"stringField\":\"foo\",\"intField\":10,\"longField\":100,\"doubleField\":3.14,\"booleanField\":true,\"enumField\":\"YELLOW\"}"); + } + @Test public void bytes_field_can_not_be_converted() { assertThatThrownBy(() -> { diff --git a/sonar-core/src/test/protobuf/test.proto b/sonar-core/src/test/protobuf/test.proto index e81ca6cffbe..46e4ad106ea 100644 --- a/sonar-core/src/test/protobuf/test.proto +++ b/sonar-core/src/test/protobuf/test.proto @@ -32,6 +32,7 @@ enum FakeEnum { BLUE = 0; RED = 1; GREEN = 2; + FakeEnum_YELLOW = 3; } message PrimitiveTypeMsg { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/ExternalIssueImporterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/ExternalIssueImporterTest.java index bdbd28fcdaa..e8ff234901d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/ExternalIssueImporterTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/ExternalIssueImporterTest.java @@ -46,6 +46,7 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.issue.impact.Severity.BLOCKER; import static org.sonar.api.issue.impact.Severity.HIGH; import static org.sonar.api.issue.impact.Severity.LOW; import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; @@ -109,7 +110,7 @@ public class ExternalIssueImporterTest { ExternalIssue output = context.allExternalIssues().iterator().next(); assertThat(output.engineId()).isEqualTo(RULE_ENGINE_ID); assertThat(output.ruleId()).isEqualTo(RULE_ID); - assertThat(output.severity()).isEqualTo(Severity.CRITICAL); //backmapped + assertThat(output.severity()).isEqualTo(Severity.BLOCKER); // backmapped assertThat(output.type()).isEqualTo(RuleType.VULNERABILITY); //backmapped assertThat(output.remediationEffort()).isNull(); assertThat(logs.logs(Level.INFO)).contains("Imported 1 issue in 1 file"); @@ -119,10 +120,10 @@ public class ExternalIssueImporterTest { assertThat(output1.ruleId()).isEqualTo(RULE_ID); assertThat(output1.name()).isEqualTo(RULE_NAME); assertThat(output1.engineId()).isEqualTo(RULE_ENGINE_ID); - assertThat(output1.severity()).isEqualTo(Severity.CRITICAL); //backmapped + assertThat(output1.severity()).isEqualTo(Severity.BLOCKER); // backmapped assertThat(output1.type()).isEqualTo(RuleType.VULNERABILITY); //backmapped assertThat(output1.cleanCodeAttribute()).isEqualTo(RULE_ATTRIBUTE); - assertThat(output1.defaultImpacts()).containsExactlyInAnyOrderEntriesOf(Map.of(SECURITY, HIGH, MAINTAINABILITY, LOW)); + assertThat(output1.defaultImpacts()).containsExactlyInAnyOrderEntriesOf(Map.of(SECURITY, BLOCKER, MAINTAINABILITY, LOW)); } @Test @@ -333,7 +334,7 @@ public class ExternalIssueImporterTest { private static ExternalIssueReport.Rule createRule() { - return createRule(RULE_ATTRIBUTE.name(), SECURITY.name(), HIGH.name()); + return createRule(RULE_ATTRIBUTE.name(), SECURITY.name(), BLOCKER.name()); } private static ExternalIssueReport.Rule createRule(String cleanCodeAttribute, String softwareQuality, String impactSeverity) { diff --git a/sonar-ws/src/main/protobuf/ws-commons.proto b/sonar-ws/src/main/protobuf/ws-commons.proto index 7fa09b59828..e999f837afd 100644 --- a/sonar-ws/src/main/protobuf/ws-commons.proto +++ b/sonar-ws/src/main/protobuf/ws-commons.proto @@ -121,6 +121,9 @@ enum ImpactSeverity { LOW = 1; MEDIUM = 2; HIGH = 3; + // INFO and BLOCKER conflicts with Severity enum, so we use different values prefixed with enum name + ImpactSeverity_INFO = 4; + ImpactSeverity_BLOCKER = 5; } // Lines start at 1 and line offsets start at 0