aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>2024-10-10 16:03:21 +0200
committersonartech <sonartech@sonarsource.com>2024-10-16 20:03:01 +0000
commit32cec354bfc868d591e6356ee85728c5ecba51d1 (patch)
treeee060a563c76cd76f1cb82798957a161dcd7c304
parent9be6b7091b066077e7222b3885e2b403f29f0706 (diff)
downloadsonarqube-32cec354bfc868d591e6356ee85728c5ecba51d1.tar.gz
sonarqube-32cec354bfc868d591e6356ee85728c5ecba51d1.zip
SONAR-23250 Update activate_rule endpoint to support impacts
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java6
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRuleActionIT.java107
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ActivateRuleAction.java44
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfileWsParameters.java2
4 files changed, 155 insertions, 4 deletions
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java
index 5cf81f7a547..a155d284fb9 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java
@@ -68,6 +68,12 @@ public class RuleActivation {
return new RuleActivation(ruleUuid, false, severity, prioritizedRule, parameters, Map.of());
}
+ public static RuleActivation create(String ruleUuid, @Nullable String severity, Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impactSeverities,
+ @Nullable Boolean prioritizedRule,
+ @Nullable Map<String, String> parameters) {
+ return new RuleActivation(ruleUuid, false, severity, prioritizedRule, parameters, impactSeverities);
+ }
+
public static RuleActivation create(String ruleUuid, @Nullable String severity, @Nullable Map<String, String> parameters) {
return create(ruleUuid, severity, null, parameters);
}
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRuleActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRuleActionIT.java
index 72e33b8b962..e13a7d15b66 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRuleActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRuleActionIT.java
@@ -21,17 +21,21 @@ package org.sonar.server.qualityprofile.ws;
import java.net.HttpURLConnection;
import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.MockitoAnnotations;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.ws.WebService;
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.permission.GlobalPermission;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.RuleDto;
@@ -55,9 +59,11 @@ import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_IMPACTS;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_KEY;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PRIORITIZED_RULE;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_RULE;
+import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_SEVERITY;
class ActivateRuleActionIT {
@@ -83,7 +89,7 @@ class ActivateRuleActionIT {
WebService.Action definition = ws.getDef();
assertThat(definition).isNotNull();
assertThat(definition.isPost()).isTrue();
- assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("severity", "prioritizedRule", "key",
+ assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("severity", "impacts", "prioritizedRule", "key",
"reset", "rule", "params");
}
@@ -144,10 +150,69 @@ class ActivateRuleActionIT {
}
@Test
- void activate_rule() {
+ void handle_whenBothSeverityAndImpactsAreSent_shouldFail() {
userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_PROFILES);
QProfileDto qualityProfile = db.qualityProfiles().insert();
RuleDto rule = db.rules().insert(RuleTesting.randomRuleKey());
+
+ TestRequest request = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RULE, rule.getKey().toString())
+ .setParam(PARAM_KEY, qualityProfile.getKee())
+ .setParam(PARAM_SEVERITY, "BLOCKER")
+ .setParam(PARAM_IMPACTS, "MAINTAINABILITY=BLOCKER");
+
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("'severity' and 'impacts' parameters can't be provided both at the same time");
+ }
+
+ @Test
+ void handle_whenImpactsAreMalformed_shouldFail() {
+ userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_PROFILES);
+ QProfileDto qualityProfile = db.qualityProfiles().insert();
+ RuleDto rule = db.rules().insert(RuleTesting.randomRuleKey());
+
+ TestRequest request = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RULE, rule.getKey().toString())
+ .setParam(PARAM_KEY, qualityProfile.getKee())
+ .setParam(PARAM_IMPACTS, "MAINTAINABILITY=UNKNOWN_SEVERITY");
+
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("Unexpected value for parameter 'impacts': MAINTAINABILITY=UNKNOWN_SEVERITY");
+ }
+
+ @Test
+ void handle_whenImpactsAreProvidedAndDoesntMatchRuleImpacts_shouldFail() {
+ userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_PROFILES);
+ QProfileDto qualityProfile = db.qualityProfiles().insert();
+ RuleDto rule = db.rules().insert(r -> r.setRuleKey(RuleTesting.randomRuleKey()).setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ newImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))));
+
+ TestRequest request = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RULE, rule.getKey().toString())
+ .setParam(PARAM_KEY, qualityProfile.getKee())
+ .setParam(PARAM_IMPACTS, "MAINTAINABILITY=BLOCKER;SECURITY=MEDIUM");
+
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("Only impacts defined on the rule can be overridden. (MAINTAINABILITY)");
+ }
+
+ @Test
+ void activate_rule() {
+ userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_PROFILES);
+ QProfileDto qualityProfile = db.qualityProfiles().insert();
+ RuleDto rule = db.rules().insert(r -> r.setRuleKey(RuleTesting.randomRuleKey()).setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ newImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ newImpact(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.LOW),
+ newImpact(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.INFO))));
+
TestRequest request = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_RULE, rule.getKey().toString())
@@ -168,11 +233,49 @@ class ActivateRuleActionIT {
RuleActivation activation = activations.iterator().next();
assertThat(activation.getRuleUuid()).isEqualTo(rule.getUuid());
assertThat(activation.getSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(activation.getImpactSeverities()).isEmpty();
assertThat(activation.isPrioritizedRule()).isTrue();
assertThat(activation.isReset()).isFalse();
}
@Test
+ void handle_whenImpactsAreProvided_shouldOverrideImpacts() {
+ userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_PROFILES);
+ QProfileDto qualityProfile = db.qualityProfiles().insert();
+ RuleDto rule = db.rules().insert(r -> r.setRuleKey(RuleTesting.randomRuleKey()).setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ newImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ newImpact(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.LOW),
+ newImpact(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.INFO))));
+
+ TestRequest request = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RULE, rule.getKey().toString())
+ .setParam(PARAM_KEY, qualityProfile.getKee())
+ .setParam(PARAM_IMPACTS, "MAINTAINABILITY=BLOCKER;SECURITY=MEDIUM");
+
+ TestResponse response = request.execute();
+
+ assertThat(response.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
+ verify(qProfileRules).activateAndCommit(any(DbSession.class), any(QProfileDto.class), ruleActivationCaptor.capture());
+
+ Collection<RuleActivation> activations = ruleActivationCaptor.getValue();
+ assertThat(activations).hasSize(1);
+
+ RuleActivation activation = activations.iterator().next();
+ assertThat(activation.getRuleUuid()).isEqualTo(rule.getUuid());
+ assertThat(activation.getSeverity()).isNull();
+ assertThat(activation.getImpactSeverities()).containsExactlyInAnyOrderEntriesOf(
+ Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.BLOCKER, SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM));
+ assertThat(activation.isPrioritizedRule()).isNull();
+ assertThat(activation.isReset()).isFalse();
+ }
+
+ private static ImpactDto newImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {
+ return new ImpactDto().setSoftwareQuality(softwareQuality).setSeverity(severity);
+ }
+
+ @Test
void as_qprofile_editor() {
UserDto user = db.users().insertUser();
QProfileDto qualityProfile = db.qualityProfiles().insert();
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ActivateRuleAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ActivateRuleAction.java
index 93b149595cb..39dee38f0d5 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ActivateRuleAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ActivateRuleAction.java
@@ -19,7 +19,11 @@
*/
package org.sonar.server.qualityprofile.ws;
+import java.util.EnumMap;
import java.util.Map;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.ws.Change;
@@ -31,6 +35,7 @@ import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.RuleDto;
+import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.qualityprofile.QProfileRules;
import org.sonar.server.qualityprofile.RuleActivation;
import org.sonar.server.user.UserSession;
@@ -40,6 +45,7 @@ import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_ACTIVATE_RULE;
+import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_IMPACTS;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_KEY;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PARAMS;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PRIORITIZED_RULE;
@@ -71,6 +77,7 @@ public class ActivateRuleAction implements QProfileWsAction {
" <li>Edit right on the specified quality profile</li>" +
"</ul>")
.setChangelog(
+ new Change("10.8", format("Add new parameter '%s'", PARAM_IMPACTS)),
new Change("10.6", format("Add parameter '%s'.", PARAM_PRIORITIZED_RULE)),
new Change("10.2", format("Parameter '%s' is now deprecated.", PARAM_SEVERITY)))
.setHandler(this)
@@ -88,10 +95,14 @@ public class ActivateRuleAction implements QProfileWsAction {
.setExampleValue("java:AvoidCycles");
activate.createParam(PARAM_SEVERITY)
- .setDescription(format("Severity. Ignored if parameter %s is true.", PARAM_RESET))
+ .setDescription(format("Severity. Cannot be used as the same time as '%s'.Ignored if parameter %s is true.", PARAM_IMPACTS, PARAM_RESET))
.setDeprecatedSince("10.2")
.setPossibleValues(Severity.ALL);
+ activate.createParam(PARAM_IMPACTS)
+ .setDescription(format("Override of impact severities for the rule. Cannot be used as the same time as '%s'. Ignored if parameter %s is true.", PARAM_SEVERITY, PARAM_RESET))
+ .setExampleValue("impacts=MAINTAINABILITY=HIGH;SECURITY=MEDIUM");
+
activate.createParam(PARAM_PARAMS)
.setDescription(format("Parameters as semi-colon list of <code>key=value</code>. Ignored if parameter %s is true.", PARAM_RESET))
.setExampleValue("params=key1=v1;key2=v2");
@@ -129,13 +140,42 @@ public class ActivateRuleAction implements QProfileWsAction {
return RuleActivation.createReset(ruleDto.getUuid());
}
String severity = request.param(PARAM_SEVERITY);
+ String impactsAsString = request.param(PARAM_IMPACTS);
+
+ if (impactsAsString != null && severity != null) {
+ throw BadRequestException.create(format("'%s' and '%s' parameters can't be provided both at the same time", PARAM_SEVERITY, PARAM_IMPACTS));
+ }
+
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = new EnumMap<>(SoftwareQuality.class);
+ if (impactsAsString != null) {
+ impacts = getImpacts(impactsAsString, ruleDto);
+ }
+
Boolean prioritizedRule = request.paramAsBoolean(PARAM_PRIORITIZED_RULE);
Map<String, String> params = null;
String paramsAsString = request.param(PARAM_PARAMS);
if (paramsAsString != null) {
params = KeyValueFormat.parse(paramsAsString);
}
- return RuleActivation.create(ruleDto.getUuid(), severity, prioritizedRule, params);
+ return RuleActivation.create(ruleDto.getUuid(), severity, impacts, prioritizedRule, params);
+ }
+
+ @NotNull
+ private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> getImpacts(String impactsAsString, RuleDto ruleDto) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> result;
+ try {
+ result = KeyValueFormat.parse(impactsAsString)
+ .entrySet()
+ .stream().collect(Collectors.toMap(e -> SoftwareQuality.valueOf(e.getKey()), e -> org.sonar.api.issue.impact.Severity.valueOf(e.getValue())));
+ } catch (Exception e) {
+ throw BadRequestException.create(format("Unexpected value for parameter '%s': %s", PARAM_IMPACTS, impactsAsString));
+ }
+ if (!ruleDto.getDefaultImpactsMap().keySet().containsAll(result.keySet())) {
+ throw BadRequestException.create(
+ format("Only impacts defined on the rule can be overridden. (%s)", ruleDto.getDefaultImpactsMap().keySet().stream().map(Enum::name).collect(Collectors.joining(","))));
+ }
+ return result;
+
}
}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfileWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfileWsParameters.java
index ae9a36ba880..4d39e64bdea 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfileWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfileWsParameters.java
@@ -27,6 +27,7 @@ public class QualityProfileWsParameters {
String PARAM_BACKUP = "backup";
}
+
public static final String ACTION_ACTIVATE_RULE = "activate_rule";
public static final String ACTION_ACTIVATE_RULES = "activate_rules";
public static final String ACTION_ADD_PROJECT = "add_project";
@@ -70,6 +71,7 @@ public class QualityProfileWsParameters {
public static final String PARAM_TO = "to";
public static final String PARAM_TO_NAME = "toName";
public static final String PARAM_PRIORITIZED_RULE = "prioritizedRule";
+ public static final String PARAM_IMPACTS = "impacts";
private QualityProfileWsParameters() {
// Only static stuff