import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference.ValueDifference;
+import java.util.List;
+import java.util.Map;
import org.assertj.core.data.MapEntry;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.sonar.api.config.Configuration;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.rule.RuleParamType;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
+import org.sonar.db.issue.ImpactDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.QualityProfileTesting;
import org.sonar.db.rule.RuleDto;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.sonar.api.issue.impact.Severity.BLOCKER;
+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;
+import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
-public class QProfileComparisonIT {
+class QProfileComparisonIT {
- @Rule
- public UserSessionRule userSession = UserSessionRule.standalone().anonymous();
- @Rule
- public DbTester dbTester = DbTester.create();
- @Rule
- public EsTester es = EsTester.create();
+ @RegisterExtension
+ private final UserSessionRule userSession = UserSessionRule.standalone().anonymous();
+ @RegisterExtension
+ private final DbTester dbTester = DbTester.create();
+ @RegisterExtension
+ private final EsTester es = EsTester.create();
private DbSession dbSession;
private QProfileRules qProfileRules;
private QProfileDto left;
private QProfileDto right;
- @Before
- public void before() {
+ @BeforeEach
+ void before() {
DbClient db = dbTester.getDbClient();
dbSession = db.openSession(false);
RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE);
qProfileRules = new QProfileRulesImpl(db, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService);
comparison = new QProfileComparison(db);
- xooRule1 = RuleTesting.newXooX1().setSeverity("MINOR");
- xooRule2 = RuleTesting.newXooX2().setSeverity("MAJOR");
+ xooRule1 = RuleTesting.newXooX1().setSeverity("MINOR").replaceAllDefaultImpacts(List.of(new ImpactDto(SECURITY, LOW)));
+ xooRule2 = RuleTesting.newXooX2().setSeverity("MAJOR").replaceAllDefaultImpacts(List.of(new ImpactDto(MAINTAINABILITY, BLOCKER)));
db.ruleDao().insert(dbSession, xooRule1);
db.ruleDao().insert(dbSession, xooRule2);
db.ruleDao().insertRuleParam(dbSession, xooRule1, RuleParamDto.createFor(xooRule1)
dbSession.commit();
}
- @After
- public void after() {
+ @AfterEach
+ void after() {
dbSession.close();
}
@Test
- public void compare_empty_profiles() {
+ void compare_empty_profiles() {
QProfileComparisonResult result = comparison.compare(dbSession, left, right);
assertThat(result.left().getKee()).isEqualTo(left.getKee());
assertThat(result.right().getKee()).isEqualTo(right.getKee());
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isEmpty();
- assertThat(result.collectRuleKeys()).isEmpty();
+ assertThat(result.getImpactedRules()).isEmpty();
}
@Test
- public void compare_same() {
+ void compare_same() {
RuleActivation commonActivation = RuleActivation.create(xooRule1.getUuid(), Severity.CRITICAL,
+ Map.of(SECURITY, BLOCKER),
+ false,
ImmutableMap.of("min", "7", "max", "42"));
qProfileRules.activateAndCommit(dbSession, left, singleton(commonActivation));
qProfileRules.activateAndCommit(dbSession, right, singleton(commonActivation));
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isEmpty();
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
}
@Test
- public void compare_only_left() {
+ void compare_only_left() {
RuleActivation activation = RuleActivation.create(xooRule1.getUuid());
qProfileRules.activateAndCommit(dbSession, left, singleton(activation));
assertThat(result.inLeft()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isEmpty();
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
}
@Test
- public void compare_only_right() {
+ void compare_only_right() {
qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule1.getUuid())));
QProfileComparisonResult result = comparison.compare(dbSession, left, right);
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
assertThat(result.modified()).isEmpty();
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
}
@Test
- public void compare_disjoint() {
+ void compare_disjoint() {
qProfileRules.activateAndCommit(dbSession, left, singleton(RuleActivation.create(xooRule1.getUuid())));
qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule2.getUuid())));
assertThat(result.inLeft()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
assertThat(result.inRight()).isNotEmpty().containsOnlyKeys(xooRule2.getKey());
assertThat(result.modified()).isEmpty();
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey(), xooRule2.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey(), xooRule2.getKey());
}
@Test
- public void compare_modified_severity() {
+ void compare_modified_severity() {
qProfileRules.activateAndCommit(dbSession, left, singleton(RuleActivation.create(xooRule1.getUuid(), Severity.CRITICAL, null)));
qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule1.getUuid(), Severity.BLOCKER, null)));
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
ActiveRuleDiff activeRuleDiff = result.modified().get(xooRule1.getKey());
assertThat(activeRuleDiff.leftSeverity()).isEqualTo(Severity.CRITICAL);
}
@Test
- public void compare_modified_param() {
+ void compare_modified_impacts() {
+ qProfileRules.activateAndCommit(dbSession, left, singleton(RuleActivation.create(xooRule1.getUuid(), null,
+ Map.of(SECURITY, BLOCKER), null, null)));
+ qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule1.getUuid(), null,
+ Map.of(SECURITY, MEDIUM), null, null)));
+
+ QProfileComparisonResult result = comparison.compare(dbSession, left, right);
+ assertThat(result.left().getKee()).isEqualTo(left.getKee());
+ assertThat(result.right().getKee()).isEqualTo(right.getKee());
+ assertThat(result.same()).isEmpty();
+ assertThat(result.inLeft()).isEmpty();
+ assertThat(result.inRight()).isEmpty();
+ assertThat(result.modified()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
+
+ ActiveRuleDiff activeRuleDiff = result.modified().get(xooRule1.getKey());
+ assertThat(activeRuleDiff.impactDifference().entriesDiffering()).isNotEmpty();
+ ValueDifference<org.sonar.api.issue.impact.Severity> impactdiff = activeRuleDiff.impactDifference().entriesDiffering().get(SECURITY);
+ assertThat(impactdiff.leftValue()).isEqualTo(BLOCKER);
+ assertThat(impactdiff.rightValue()).isEqualTo(MEDIUM);
+
+ assertThat(activeRuleDiff.paramDifference().areEqual()).isTrue();
+ }
+
+ @Test
+ void compare_modified_param() {
qProfileRules.activateAndCommit(dbSession, left, singleton(RuleActivation.create(xooRule1.getUuid(), null, ImmutableMap.of("max", "20"))));
qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule1.getUuid(), null, ImmutableMap.of("max", "30"))));
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
ActiveRuleDiff activeRuleDiff = result.modified().get(xooRule1.getKey());
assertThat(activeRuleDiff.leftSeverity()).isEqualTo(activeRuleDiff.rightSeverity()).isEqualTo(xooRule1.getSeverityString());
}
@Test
- public void compare_different_params() {
+ void compare_different_params() {
qProfileRules.activateAndCommit(dbSession, left, singleton(RuleActivation.create(xooRule1.getUuid(), null, ImmutableMap.of("max", "20"))));
qProfileRules.activateAndCommit(dbSession, right, singleton(RuleActivation.create(xooRule1.getUuid(), null, ImmutableMap.of("min", "5"))));
assertThat(result.inLeft()).isEmpty();
assertThat(result.inRight()).isEmpty();
assertThat(result.modified()).isNotEmpty().containsOnlyKeys(xooRule1.getKey());
- assertThat(result.collectRuleKeys()).containsOnly(xooRule1.getKey());
+ assertThat(result.getImpactedRules()).containsOnlyKeys(xooRule1.getKey());
ActiveRuleDiff activeRuleDiff = result.modified().get(xooRule1.getKey());
assertThat(activeRuleDiff.leftSeverity()).isEqualTo(activeRuleDiff.rightSeverity()).isEqualTo(xooRule1.getSeverityString());
*/
package org.sonar.server.qualityprofile.ws;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.db.rule.RuleRepositoryDto;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.qualityprofile.QProfileComparison;
-import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import static java.util.Collections.singletonList;
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.Severity.MEDIUM;
+import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
-public class CompareActionIT {
+class CompareActionIT {
- @Rule
- public DbTester db = DbTester.create();
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
- @Rule
- public UserSessionRule userSession = UserSessionRule.standalone();
+ @RegisterExtension
+ private final DbTester db = DbTester.create();
private final DbClient dbClient = db.getDbClient();
private final DbSession session = db.getSession();
new CompareAction(db.getDbClient(), new QProfileComparison(db.getDbClient()), new Languages(LanguageTesting.newLanguage("xoo", "Xoo"))));
@Test
- public void compare_nominal() {
+ void compare_nominal() {
createRepository("blah", "xoo", "Blah");
RuleDto rule1 = createRule("xoo", "rule1");
RuleDto rule3 = createRule("xoo", "rule3");
RuleDto rule4 = createRuleWithParam("xoo", "rule4");
RuleDto rule5 = createRule("xoo", "rule5");
+ RuleDto rule6 = createRuleWithImpacts("xoo", "rule6", List.of(new ImpactDto(SECURITY, BLOCKER)));
/*
* Profile 1:
* - rule 2 active (only in this profile) => "inLeft"
* - rule 4 active with different parameters => "modified"
* - rule 5 active with different severity => "modified"
+ * - rule 6 active with different impacts => "modified"
*/
QProfileDto profile1 = createProfile("xoo", "Profile 1", "xoo-profile-1-01234");
createActiveRule(rule1, profile1);
createActiveRule(rule2, profile1);
createActiveRuleWithParam(rule4, profile1, "polop");
createActiveRuleWithSeverity(rule5, profile1, Severity.MINOR);
+ createActiveRuleWithImpacts(rule6, profile1, Map.of(SECURITY, BLOCKER));
session.commit();
/*
* - rule 1 active (on both profiles) => "same"
* - rule 3 active (only in this profile) => "inRight"
* - rule 4 active with different parameters => "modified"
+ * - rule 6 active with different impacts => "modified"
*/
QProfileDto profile2 = createProfile("xoo", "Profile 2", "xoo-profile-2-12345");
createActiveRule(rule1, profile2);
createActiveRule(rule3, profile2);
createActiveRuleWithParam(rule4, profile2, "palap");
createActiveRuleWithSeverity(rule5, profile2, Severity.MAJOR);
+ createActiveRuleWithImpacts(rule6, profile2, Map.of(SECURITY, HIGH));
session.commit();
ws.newRequest()
}
@Test
- public void compare_whenSecurityHotspot_shouldReturnEmptyCleanCodeInformation() {
+ void compare_whenSecurityHotspot_shouldReturnEmptyCleanCodeInformation() {
createRepository("blah", "xoo", "Blah");
RuleDto rule1 = createSecurityHotspot("xoo", "rule1");
}
@Test
- public void compare_param_on_left() {
+ void compare_param_on_left() {
RuleDto rule1 = createRuleWithParam("xoo", "rule1");
createRepository("blah", "xoo", "Blah");
QProfileDto profile1 = createProfile("xoo", "Profile 1", "xoo-profile-1-01234");
}
@Test
- public void compare_param_on_right() {
+ void compare_param_on_right() {
RuleDto rule1 = createRuleWithParam("xoo", "rule1");
createRepository("blah", "xoo", "Blah");
QProfileDto profile1 = createProfile("xoo", "Profile 1", "xoo-profile-1-01234");
}
@Test
- public void fail_on_missing_left_param() {
+ void compare_impacts() {
+ RuleDto rule1 = createRuleWithImpacts("xoo", "impactRule", List.of(new ImpactDto(SECURITY, MEDIUM)));
+ createRepository("blah", "xoo", "Blah");
+ QProfileDto profile1 = createProfile("xoo", "Profile 1", "xoo-profile-1-01234");
+ createActiveRuleWithImpacts(rule1, profile1, Map.of(SECURITY, MEDIUM, RELIABILITY, HIGH));
+ QProfileDto profile2 = createProfile("xoo", "Profile 2", "xoo-profile-2-12345");
+ createActiveRuleWithImpacts(rule1, profile2, Map.of(SECURITY, BLOCKER));
+ session.commit();
+
+ ws.newRequest()
+ .setParam("leftKey", profile1.getKee())
+ .setParam("rightKey", profile2.getKee())
+ .execute().assertJson(this.getClass(), "compare_impacts.json");
+ }
+
+ @Test
+ void compare_impacts_left_only_should_return_impacts_based_on_rule_impacts() {
+ RuleDto rule1 = createRuleWithImpacts("xoo", "impactRule", List.of(new ImpactDto(SECURITY, MEDIUM)));
+ createRepository("blah", "xoo", "Blah");
+ QProfileDto profile1 = createProfile("xoo", "Profile 1", "xoo-profile-1-01234");
+ createActiveRuleWithImpacts(rule1, profile1, Map.of(SECURITY, LOW, RELIABILITY, HIGH));
+ QProfileDto profile2 = createProfile("xoo", "Profile 2", "xoo-profile-2-12345");
+ session.commit();
+
+ ws.newRequest()
+ .setParam("leftKey", profile1.getKee())
+ .setParam("rightKey", profile2.getKee())
+ .execute().assertJson(this.getClass(), "compare_impacts_left_only.json");
+ }
+
+ @Test
+ void fail_on_missing_left_param() {
assertThatThrownBy(() -> {
ws.newRequest()
.setParam("rightKey", "polop")
}
@Test
- public void fail_on_missing_right_param() {
+ void fail_on_missing_right_param() {
assertThatThrownBy(() -> {
ws.newRequest()
.setParam("leftKey", "polop")
}
@Test
- public void fail_on_left_profile_not_found() {
+ void fail_on_left_profile_not_found() {
createProfile("xoo", "Right", "xoo-right-12345");
assertThatThrownBy(() -> {
}
@Test
- public void fail_on_right_profile_not_found() {
+ void fail_on_right_profile_not_found() {
createProfile("xoo", "Left", "xoo-left-12345");
assertThatThrownBy(() -> {
}
@Test
- public void definition() {
+ void definition() {
WebService.Action definition = ws.getDef();
assertThat(definition).isNotNull();
assertThat(definition.isPost()).isFalse();
return ruleDto;
}
+ private RuleDto createRuleWithImpacts(String lang, String id, Collection<ImpactDto> impacts) {
+ RuleDto rule = createFor(RuleKey.of("blah", id))
+ .setUuid(Uuids.createFast())
+ .setName(StringUtils.capitalize(id))
+ .setLanguage(lang)
+ .setSeverity(Severity.BLOCKER)
+ .setScope(Scope.MAIN)
+ .setStatus(RuleStatus.READY)
+ .setCleanCodeAttribute(CleanCodeAttribute.EFFICIENT)
+ .replaceAllDefaultImpacts(impacts);
+ RuleDto ruleDto = rule;
+ dbClient.ruleDao().insert(session, ruleDto);
+ return ruleDto;
+ }
+
private RuleDto createSecurityHotspot(String lang, String id) {
RuleDto rule = createFor(RuleKey.of("blah", id))
.setUuid(Uuids.createFast())
private ActiveRuleDto createActiveRule(RuleDto rule, QProfileDto profile) {
ActiveRuleDto activeRule = ActiveRuleDto.createFor(profile, rule)
- .setSeverity(rule.getSeverityString());
+ .setSeverity(rule.getSeverityString())
+ .setImpacts(rule.getDefaultImpactsMap());
+ dbClient.activeRuleDao().insert(session, activeRule);
+ return activeRule;
+ }
+
+ private ActiveRuleDto createActiveRuleWithImpacts(RuleDto rule, QProfileDto profile, Map<SoftwareQuality,
+ org.sonar.api.issue.impact.Severity> impacts) {
+ ActiveRuleDto activeRule = ActiveRuleDto.createFor(profile, rule)
+ .setSeverity(rule.getSeverityString())
+ .setImpacts(impacts);
dbClient.activeRuleDao().insert(session, activeRule);
return activeRule;
}
private ActiveRuleDto createActiveRuleWithSeverity(RuleDto rule, QProfileDto profile, String severity) {
ActiveRuleDto activeRule = ActiveRuleDto.createFor(profile, rule)
- .setSeverity(severity);
+ .setSeverity(severity)
+ .setImpacts(rule.getDefaultImpactsMap());
dbClient.activeRuleDao().insert(session, activeRule);
return activeRule;
}
--- /dev/null
+{
+ "left": {
+ "key": "xoo-profile-1-01234",
+ "name": "Profile 1"
+ },
+ "right": {
+ "key": "xoo-profile-2-12345",
+ "name": "Profile 2"
+ },
+ "inLeft": [],
+ "inRight": [],
+ "modified": [
+ {
+ "key": "blah:impactRule",
+ "name": "ImpactRule",
+ "pluginKey": "blah",
+ "pluginName": "Blah",
+ "languageKey": "xoo",
+ "languageName": "Xoo",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "left": {
+ "severity": "BLOCKER",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "MEDIUM"
+ }
+ ],
+ "params": {}
+ },
+ "right": {
+ "severity": "BLOCKER",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "BLOCKER"
+ }
+
+ ],
+ "params": {}
+ }
+ }
+ ],
+ "same": []
+}
--- /dev/null
+{
+ "left": {
+ "key": "xoo-profile-1-01234",
+ "name": "Profile 1"
+ },
+ "right": {
+ "key": "xoo-profile-2-12345",
+ "name": "Profile 2"
+ },
+ "inLeft": [
+ {
+ "key": "blah:impactRule",
+ "name": "ImpactRule",
+ "pluginKey": "blah",
+ "pluginName": "Blah",
+ "languageKey": "xoo",
+ "languageName": "Xoo",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "LOW"
+ }
+ ]
+ }
+ ],
+ "inRight": [],
+ "modified": [],
+ "same": []
+}
"languageName": "Xoo",
"name" : "Rule4",
"cleanCodeAttributeCategory": "INTENTIONAL",
- "impacts": [
- {
- "softwareQuality": "RELIABILITY",
- "severity": "HIGH"
- }
- ],
"left" : {
"severity" : "BLOCKER",
+ "impacts": [],
"params" : {
"param_rule4" : "polop"
}
},
"right" : {
"severity" : "BLOCKER",
+ "impacts": [],
"params" : {
"param_rule4" : "palap"
}
"languageName": "Xoo",
"name" : "Rule5",
"cleanCodeAttributeCategory": "INTENTIONAL",
- "impacts": [
- {
- "softwareQuality": "RELIABILITY",
- "severity": "HIGH"
- }
- ],
"left" : {
- "severity" : "MINOR"
+ "severity" : "MINOR",
+ "impacts": []
},
"right" : {
- "severity" : "MAJOR"
+ "severity" : "MAJOR",
+ "impacts": []
+ }
+ },
+ {
+ "key": "blah:rule6",
+ "name": "Rule6",
+ "pluginKey": "blah",
+ "pluginName": "Blah",
+ "languageKey": "xoo",
+ "languageName": "Xoo",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "left": {
+ "severity": "BLOCKER",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "BLOCKER"
+ }
+ ],
+ "params": {}
+ },
+ "right": {
+ "severity": "BLOCKER",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
+ "params": {}
}
}
]
"name" : "Rule1",
"left" : {
"severity" : "BLOCKER",
+ "impacts": [],
"params" : {
"param_rule1" : "polop"
}
},
"right" : {
- "severity" : "BLOCKER"
+ "severity" : "BLOCKER",
+ "impacts": []
}
}
]
"languageName": "Xoo",
"name" : "Rule1",
"left" : {
- "severity" : "BLOCKER"
+ "severity" : "BLOCKER",
+ "impacts": []
},
"right" : {
"severity" : "BLOCKER",
+ "impacts": [],
"params" : {
"param_rule1" : "polop"
}
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
-import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.ServerSide;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.issue.ImpactDto;
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.ActiveRuleParamDto;
import org.sonar.db.qualityprofile.OrgActiveRuleDto;
import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
@ServerSide
public class QProfileComparison {
private final DbClient dbClient;
-
public QProfileComparison(DbClient dbClient) {
this.dbClient = dbClient;
}
allRules.addAll(leftActiveRulesByRuleKey.keySet());
allRules.addAll(rightActiveRulesByRuleKey.keySet());
- QProfileComparisonResult result = new QProfileComparisonResult(left, right);
+ List<RuleDto> referencedRules = dbClient.ruleDao().selectByKeys(dbSession, allRules);
+ Map<RuleKey, RuleDto> rulesByKey = Maps.uniqueIndex(referencedRules, RuleDto::getKey);
+
+ QProfileComparisonResult result = new QProfileComparisonResult(left, right, rulesByKey);
for (RuleKey ruleKey : allRules) {
if (!leftActiveRulesByRuleKey.containsKey(ruleKey)) {
result.inRight.put(ruleKey, rightActiveRulesByRuleKey.get(ruleKey));
} else if (!rightActiveRulesByRuleKey.containsKey(ruleKey)) {
result.inLeft.put(ruleKey, leftActiveRulesByRuleKey.get(ruleKey));
} else {
- compareActivationParams(dbSession, leftActiveRulesByRuleKey.get(ruleKey), rightActiveRulesByRuleKey.get(ruleKey), result);
+ compareActiveRules(dbSession,
+ rulesByKey.get(ruleKey),
+ leftActiveRulesByRuleKey.get(ruleKey),
+ rightActiveRulesByRuleKey.get(ruleKey),
+ result);
}
}
return result;
}
- private void compareActivationParams(DbSession session, ActiveRuleDto leftRule, ActiveRuleDto rightRule, QProfileComparisonResult result) {
+ private void compareActiveRules(DbSession session, RuleDto ruleDto, ActiveRuleDto leftRule, ActiveRuleDto rightRule,
+ QProfileComparisonResult result) {
RuleKey key = leftRule.getRuleKey();
Map<String, String> leftParams = paramDtoToMap(dbClient.activeRuleDao().selectParamsByActiveRuleUuid(session, leftRule.getUuid()));
Map<String, String> rightParams = paramDtoToMap(dbClient.activeRuleDao().selectParamsByActiveRuleUuid(session, rightRule.getUuid()));
- if (leftParams.equals(rightParams) && leftRule.getSeverityString().equals(rightRule.getSeverityString())) {
+ if (leftParams.equals(rightParams)
+ && leftRule.getSeverityString().equals(rightRule.getSeverityString())
+ && leftRule.getImpacts().equals(rightRule.getImpacts())) {
result.same.put(key, leftRule);
} else {
ActiveRuleDiff diff = new ActiveRuleDiff();
diff.leftSeverity = leftRule.getSeverityString();
diff.rightSeverity = rightRule.getSeverityString();
+ diff.impactDifference = Maps.difference(
+ computeEffectiveImpactMap(ruleDto, leftRule.getImpacts()),
+ computeEffectiveImpactMap(ruleDto, rightRule.getImpacts()));
+
diff.paramDifference = Maps.difference(leftParams, rightParams);
result.modified.put(key, diff);
}
}
+ private static Map<SoftwareQuality, Severity> computeEffectiveImpactMap(RuleDto ruleDto, Map<SoftwareQuality, Severity> activeRuleImpacts) {
+ Map<SoftwareQuality, Severity> impacts = ruleDto.getDefaultImpactsMap();
+ impacts.replaceAll(activeRuleImpacts::getOrDefault);
+ return impacts;
+ }
+
+ public static List<ImpactDto> computeEffectiveImpacts(RuleDto ruleDto, Map<SoftwareQuality, Severity> activeRuleImpacts) {
+ Map<SoftwareQuality, Severity> impacts = computeEffectiveImpactMap(ruleDto, activeRuleImpacts);
+ return impacts.entrySet().stream().map(e -> new ImpactDto(e.getKey(), e.getValue())).toList();
+ }
+
private Map<RuleKey, OrgActiveRuleDto> loadActiveRules(DbSession dbSession, QProfileDto profile) {
return Maps.uniqueIndex(dbClient.activeRuleDao().selectByProfile(dbSession, profile), ActiveRuleDto::getRuleKey);
}
public static class QProfileComparisonResult {
+ private final Map<RuleKey, RuleDto> rulesByKey;
private final QProfileDto left;
private final QProfileDto right;
private final Map<RuleKey, ActiveRuleDto> inLeft = new HashMap<>();
private final Map<RuleKey, ActiveRuleDiff> modified = new HashMap<>();
private final Map<RuleKey, ActiveRuleDto> same = new HashMap<>();
- public QProfileComparisonResult(QProfileDto left, QProfileDto right) {
+ public QProfileComparisonResult(QProfileDto left, QProfileDto right, Map<RuleKey, RuleDto> rulesByKey) {
this.left = left;
this.right = right;
+ this.rulesByKey = rulesByKey;
}
public QProfileDto left() {
return same;
}
- public Collection<RuleKey> collectRuleKeys() {
- Set<RuleKey> keys = new HashSet<>();
- keys.addAll(inLeft.keySet());
- keys.addAll(inRight.keySet());
- keys.addAll(modified.keySet());
- keys.addAll(same.keySet());
- return keys;
+ public Map<RuleKey, RuleDto> getImpactedRules() {
+ return rulesByKey;
}
}
public static class ActiveRuleDiff {
private String leftSeverity;
private String rightSeverity;
+ private MapDifference<SoftwareQuality, Severity> impactDifference;
private MapDifference<String, String> paramDifference;
public String leftSeverity() {
return rightSeverity;
}
+ public MapDifference<SoftwareQuality, Severity> impactDifference() {
+ return impactDifference;
+ }
+
public MapDifference<String, String> paramDifference() {
return paramDifference;
}
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
.setChangelog(
new Change("10.3", String.format("Added '%s' and '%s' fields", ATTRIBUTE_CLEAN_CODE_ATTRIBUTE_CATEGORY, ATTRIBUTE_IMPACTS)),
new Change("10.3", String.format("Dropped '%s' field from '%s', '%s' and '%s' objects",
- ATTRIBUTE_SEVERITY, ATTRIBUTE_SAME, ATTRIBUTE_IN_LEFT, ATTRIBUTE_IN_RIGHT)));
+ ATTRIBUTE_SEVERITY, ATTRIBUTE_SAME, ATTRIBUTE_IN_LEFT, ATTRIBUTE_IN_RIGHT)),
+ new Change("10.8", "'impacts' are part of the 'left' and 'right' sections of the 'modified' array"));
compare.createParam(PARAM_LEFT_KEY)
.setDescription("Profile key.")
QProfileComparisonResult result = comparator.compare(dbSession, left, right);
- List<RuleDto> referencedRules = dbClient.ruleDao().selectByKeys(dbSession, new ArrayList<>(result.collectRuleKeys()));
- Map<RuleKey, RuleDto> rulesByKey = Maps.uniqueIndex(referencedRules, RuleDto::getKey);
+ Map<RuleKey, RuleDto> rulesByKey = result.getImpactedRules();
Map<String, RuleRepositoryDto> repositoriesByKey = Maps.uniqueIndex(dbClient.ruleRepositoryDao().selectAll(dbSession), RuleRepositoryDto::getKey);
writeResult(response.newJsonWriter(), result, rulesByKey, repositoriesByKey);
}
private void writeRules(JsonWriter json, Map<RuleKey, ActiveRuleDto> activeRules, Map<RuleKey, RuleDto> rulesByKey,
Map<String, RuleRepositoryDto> repositoriesByKey) {
json.beginArray();
- for (RuleKey key : activeRules.keySet()) {
+ for (ActiveRuleDto activeRuleDto : activeRules.values()) {
json.beginObject();
- RuleDto rule = rulesByKey.get(key);
+ RuleDto rule = rulesByKey.get(activeRuleDto.getRuleKey());
writeRule(json, rule, repositoriesByKey.get(rule.getRepositoryKey()));
+ writeActiveRuleImpacts(json, rule, activeRuleDto);
json.endObject();
}
json.endArray();
if (cleanCodeAttribute != null) {
json.prop(ATTRIBUTE_CLEAN_CODE_ATTRIBUTE_CATEGORY, cleanCodeAttribute.getAttributeCategory().toString());
}
+ }
+
+ private static void writeActiveRuleImpacts(JsonWriter json, RuleDto rule, ActiveRuleDto activeRuleDto) {
json.name(ATTRIBUTE_IMPACTS);
json.beginArray();
- for (ImpactDto impact : rule.getDefaultImpacts()) {
+ List<ImpactDto> effectiveImpacts = QProfileComparison.computeEffectiveImpacts(rule, activeRuleDto.getImpacts());
+ writeImpacts(json, effectiveImpacts);
+ json.endArray();
+ }
+
+ private static void writeImpacts(JsonWriter json, List<ImpactDto> effectiveImpacts) {
+ for (ImpactDto impact : effectiveImpacts) {
json.beginObject();
json.prop(ATTRIBUTE_IMPACT_SOFTWARE_QUALITY, impact.getSoftwareQuality().toString());
json.prop(ATTRIBUTE_IMPACT_SEVERITY, impact.getSeverity().toString());
json.endObject();
}
- json.endArray();
}
private void writeDifferences(JsonWriter json, Map<RuleKey, ActiveRuleDiff> modified, Map<RuleKey, RuleDto> rulesByKey,
json.name(ATTRIBUTE_LEFT).beginObject();
json.prop(ATTRIBUTE_SEVERITY, value.leftSeverity());
+
+
+ List<ImpactDto> leftImpacts = getLeftImpactDtos(value);
+ json.name(ATTRIBUTE_IMPACTS);
+ json.beginArray();
+ writeImpacts(json, leftImpacts);
+ json.endArray();
+
json.name(ATTRIBUTE_PARAMS).beginObject();
for (Entry<String, ValueDifference<String>> valueDiff : value.paramDifference().entriesDiffering().entrySet()) {
json.prop(valueDiff.getKey(), valueDiff.getValue().leftValue());
json.name(ATTRIBUTE_RIGHT).beginObject();
json.prop(ATTRIBUTE_SEVERITY, value.rightSeverity());
+
+ List<ImpactDto> rightImpacts = getRightImpactDtos(value);
+
+ json.name(ATTRIBUTE_IMPACTS);
+ json.beginArray();
+ writeImpacts(json, rightImpacts);
+ json.endArray();
+
json.name(ATTRIBUTE_PARAMS).beginObject();
for (Entry<String, ValueDifference<String>> valueDiff : value.paramDifference().entriesDiffering().entrySet()) {
json.prop(valueDiff.getKey(), valueDiff.getValue().rightValue());
json.endArray();
}
+ private static @NotNull List<ImpactDto> getRightImpactDtos(ActiveRuleDiff activeRuleDiff) {
+ List<ImpactDto> rightImpacts = new ArrayList<>();
+ for (Entry<SoftwareQuality, ValueDifference<Severity>> valueDiff : activeRuleDiff.impactDifference().entriesDiffering().entrySet()) {
+ rightImpacts.add(new ImpactDto(valueDiff.getKey(), valueDiff.getValue().rightValue()));
+ }
+ for (Entry<SoftwareQuality, Severity> valueDiff : activeRuleDiff.impactDifference().entriesOnlyOnRight().entrySet()) {
+ rightImpacts.add(new ImpactDto(valueDiff.getKey(), valueDiff.getValue()));
+ }
+ return rightImpacts;
+ }
+
+ private static @NotNull List<ImpactDto> getLeftImpactDtos(ActiveRuleDiff activeRuleDiff) {
+ List<ImpactDto> leftImpacts = new ArrayList<>();
+ for (Entry<SoftwareQuality, ValueDifference<Severity>> valueDiff : activeRuleDiff.impactDifference().entriesDiffering().entrySet()) {
+ leftImpacts.add(new ImpactDto(valueDiff.getKey(), valueDiff.getValue().leftValue()));
+ }
+ for (Entry<SoftwareQuality, Severity> valueDiff : activeRuleDiff.impactDifference().entriesOnlyOnLeft().entrySet()) {
+ leftImpacts.add(new ImpactDto(valueDiff.getKey(), valueDiff.getValue()));
+ }
+ return leftImpacts;
+ }
+
}
"languageName": "JavaScript",
"name" : "Avoid function with too many parameters",
"cleanCodeAttributeCategory": "ADAPTABLE",
- "impacts": [
- {
- "softwareQuality": "MAINTAINABILITY",
- "severity": "HIGH"
- }
- ],
"right" : {
"severity" : "MAJOR",
+ "impacts": [
+ {
+ "softwareQuality": "MAINTAINABILITY",
+ "severity": "HIGH"
+ }
+ ],
"params" : {
"maximumFunctionParameters" : "7"
}
},
"left" : {
"severity" : "MAJOR",
+ "impacts": [
+ {
+ "softwareQuality": "MAINTAINABILITY",
+ "severity": "MEDIUM"
+ }
+ ],
"params" : {
"maximumFunctionParameters" : "10"
}