aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-webapi
diff options
context:
space:
mode:
authorOrlovAlexander <alexander.orlov@sonarsource.com>2024-11-13 17:56:07 +0100
committersonartech <sonartech@sonarsource.com>2024-11-15 20:02:42 +0000
commitba08edfe10e04f65f956474f21802991ee5e3e75 (patch)
tree01fcfa45a1f48505e6676dffda212c6ae0aaee43 /server/sonar-webserver-webapi
parentc6a3cea9ae3c68a4b5c2ea48886c771c571fd5e3 (diff)
downloadsonarqube-ba08edfe10e04f65f956474f21802991ee5e3e75.tar.gz
sonarqube-ba08edfe10e04f65f956474f21802991ee5e3e75.zip
SONAR-23593 UpdateAction accepts impacts
Diffstat (limited to 'server/sonar-webserver-webapi')
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java129
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/ParamParsingUtils.java39
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/package-info.java23
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java10
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdate.java26
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java28
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java37
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/common/ParamParsingUtilsTest.java56
8 files changed, 317 insertions, 31 deletions
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
index 7835dec89df..5c4197ddb81 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
@@ -19,16 +19,20 @@
*/
package org.sonar.server.rule.ws;
-import org.junit.Rule;
-import org.junit.Test;
+import java.util.List;
+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.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
+import org.sonar.db.issue.ImpactDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.user.UserDto;
@@ -49,6 +53,7 @@ import org.sonarqube.ws.Rules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
@@ -68,17 +73,17 @@ import static org.sonar.server.rule.ws.UpdateAction.PARAM_REMEDIATION_FN_TYPE;
import static org.sonar.server.rule.ws.UpdateAction.PARAM_TAGS;
import static org.sonar.test.JsonAssert.assertJson;
-public class UpdateActionIT {
+class UpdateActionIT {
private static final long PAST = 10000L;
- @Rule
+ @RegisterExtension
public DbTester db = DbTester.create();
- @Rule
+ @RegisterExtension
public EsTester es = EsTester.create();
- @Rule
+ @RegisterExtension
public UserSessionRule userSession = UserSessionRule.standalone();
private final DbClient dbClient = db.getDbClient();
@@ -95,7 +100,7 @@ public class UpdateActionIT {
private final WsActionTester ws = new WsActionTester(underTest);
@Test
- public void check_definition() {
+ void check_definition() {
assertThat(ws.getDef().isPost()).isTrue();
assertThat(ws.getDef().isInternal()).isFalse();
assertThat(ws.getDef().responseExampleAsString()).isNotNull();
@@ -103,7 +108,7 @@ public class UpdateActionIT {
}
@Test
- public void update_custom_rule() {
+ void update_custom_rule() {
logInAsQProfileAdministrator();
RuleDto templateRule = db.rules().insert(
r -> r.setRuleKey(RuleKey.of("java", "S001")),
@@ -156,7 +161,7 @@ public class UpdateActionIT {
}
@Test
- public void update_tags() {
+ void update_tags() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert(setSystemTags("stag1", "stag2"), setTags("tag1", "tag2"), r -> r.setNoteData(null).setNoteUserUuid(null));
@@ -175,7 +180,7 @@ public class UpdateActionIT {
}
@Test
- public void update_rule_remediation_function() {
+ void update_rule_remediation_function() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert(
@@ -217,7 +222,7 @@ public class UpdateActionIT {
}
@Test
- public void update_note() {
+ void update_note() {
UserDto userHavingUpdatingNote = db.users().insertUser();
RuleDto rule = db.rules().insert(m -> m.setNoteData("old data").setNoteUserUuid(userHavingUpdatingNote.getUuid()));
UserDto userAuthenticated = db.users().insertUser();
@@ -241,7 +246,7 @@ public class UpdateActionIT {
}
@Test
- public void fail_to_update_custom_when_description_is_empty() {
+ void fail_to_update_custom_when_description_is_empty() {
logInAsQProfileAdministrator();
RuleDto templateRule = db.rules().insert(
r -> r.setRuleKey(RuleKey.of("java", "S001")),
@@ -269,7 +274,7 @@ public class UpdateActionIT {
}
@Test
- public void throw_IllegalArgumentException_if_trying_to_update_builtin_rule_description() {
+ void throw_IllegalArgumentException_if_trying_to_update_builtin_rule_description() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert();
@@ -285,7 +290,7 @@ public class UpdateActionIT {
}
@Test
- public void throw_ForbiddenException_if_not_profile_administrator() {
+ void throw_ForbiddenException_if_not_profile_administrator() {
userSession.logIn();
assertThatThrownBy(() -> {
@@ -295,7 +300,7 @@ public class UpdateActionIT {
}
@Test
- public void throw_UnauthorizedException_if_not_logged_in() {
+ void throw_UnauthorizedException_if_not_logged_in() {
assertThatThrownBy(() -> {
ws.newRequest().setMethod("POST").execute();
})
@@ -303,7 +308,7 @@ public class UpdateActionIT {
}
@Test
- public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
+ void returnRuleCleanCodeFields_whenEndpointIsCalled() {
UserDto userAuthenticated = db.users().insertUser();
userSession.logIn(userAuthenticated).addPermission(ADMINISTER_QUALITY_PROFILES);
@@ -325,6 +330,98 @@ public class UpdateActionIT {
assertThat(updateResponse.getRule()).extracting(Rules.Rule::getCleanCodeAttributeCategory).isEqualTo(Common.CleanCodeAttributeCategory.INTENTIONAL);
}
+ @Test
+ void update_whenImpactProvided_shouldAlsoUpdateStandardSeverity() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "RELIABILITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse)
+ .extracting(r -> r.getImpacts().getImpactsList().stream().findFirst()
+ .orElseThrow(() -> new IllegalStateException("Impact is a mandatory field in the response.")))
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactly(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER);
+ assertThat(ruleResponse.getSeverity()).isEqualTo("BLOCKER");
+ }
+
+ @Test
+ void update_whenMultipleImpactsProvided_shouldOnlyUpdateExisting() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "RELIABILITY=BLOCKER;SECURITY=BLOCKER;MAINTAINABILITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse.getImpacts().getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER),
+ tuple(Common.SoftwareQuality.SECURITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER));
+ assertThat(ruleResponse.getSeverity()).isEqualTo("BLOCKER");
+ }
+
+ @Test
+ void update_whenImpactsDontMatch_shouldNotUpdateAnything() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "SECURITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse.getImpacts().getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.MEDIUM));
+ assertThat(ruleResponse.getSeverity()).isEqualTo("MAJOR");
+ }
+
private void logInAsQProfileAdministrator() {
userSession
.logIn()
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/ParamParsingUtils.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/ParamParsingUtils.java
new file mode 100644
index 00000000000..80b957f125f
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/ParamParsingUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.common;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public class ParamParsingUtils {
+ private ParamParsingUtils() {
+ // utility class
+ }
+
+ public static Pair<SoftwareQuality, Severity> parseImpact(String impact) {
+ String[] parts = impact.split("=");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException("Invalid impact format: " + impact);
+ }
+ return Pair.of(SoftwareQuality.valueOf(parts[0]),
+ Severity.valueOf(parts[1]));
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/package-info.java
new file mode 100644
index 00000000000..fb8006c3f8f
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
index 83fea43fa58..1916e6681dc 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
@@ -50,6 +50,7 @@ import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.core.rule.ImpactSeverityMapper.mapImpactSeverity;
import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.server.common.ParamParsingUtils.parseImpact;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_SEVERITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -219,13 +220,4 @@ public class SetSeverityAction implements IssuesWsAction {
throw new IllegalArgumentException("One of the parameters 'severity' or 'impact' must be provided");
}
}
-
- private static Pair<SoftwareQuality, org.sonar.api.issue.impact.Severity> parseImpact(String impact) {
- String[] parts = impact.split("=");
- if (parts.length != 2) {
- throw new IllegalArgumentException("Invalid impact format: " + impact);
- }
- return Pair.of(SoftwareQuality.valueOf(parts[0]),
- org.sonar.api.issue.impact.Severity.valueOf(parts[1]));
- }
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdate.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdate.java
index 3cd5c21b45c..e95c76ab8f7 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdate.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdate.java
@@ -19,12 +19,15 @@
*/
package org.sonar.server.rule;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.server.debt.DebtRemediationFunction;
@@ -43,6 +46,7 @@ public class RuleUpdate {
private boolean changeName = false;
private boolean changeDescription = false;
private boolean changeSeverity = false;
+ private boolean changeImpacts = false;
private boolean changeStatus = false;
private boolean changeParameters = false;
private final RuleUpdateUseCase useCase;
@@ -53,6 +57,7 @@ public class RuleUpdate {
private String name;
private String markdownDescription;
private String severity;
+ private final Map<SoftwareQuality, Severity> impactSeverities = new EnumMap<>(SoftwareQuality.class);
private RuleStatus status;
private final Map<String, String> parameters = new HashMap<>();
@@ -140,6 +145,21 @@ public class RuleUpdate {
return this;
}
+ public Map<SoftwareQuality, Severity> getImpactSeverities() {
+ return impactSeverities;
+ }
+
+ /**
+ * Impacts to be updated (only for custom rules)
+ */
+ public RuleUpdate setImpactSeverities(Map<SoftwareQuality, Severity> impactSeverities) {
+ checkCustomRule();
+ this.impactSeverities.clear();
+ this.impactSeverities.putAll(impactSeverities);
+ changeImpacts = true;
+ return this;
+ }
+
@CheckForNull
public RuleStatus getStatus() {
return status;
@@ -200,6 +220,10 @@ public class RuleUpdate {
return changeSeverity;
}
+ public boolean isChangeImpacts() {
+ return changeImpacts;
+ }
+
public boolean isChangeStatus() {
return changeStatus;
}
@@ -213,7 +237,7 @@ public class RuleUpdate {
}
private boolean isCustomRuleFieldsEmpty() {
- return !changeName && !changeDescription && !changeSeverity && !changeStatus && !changeParameters;
+ return !changeName && !changeDescription && !changeSeverity && !changeStatus && !changeParameters && !changeImpacts;
}
private void checkCustomRule() {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java
index 5fa23689c0c..357d57bde9f 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java
@@ -27,13 +27,16 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.internal.ImpactMapper;
@@ -55,6 +58,9 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.sonar.api.server.rule.internal.ImpactMapper.convertToDeprecatedSeverity;
+import static org.sonar.api.server.rule.internal.ImpactMapper.convertToRuleType;
+import static org.sonar.core.rule.ImpactSeverityMapper.mapImpactSeverity;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
@ServerSide
@@ -109,6 +115,9 @@ public class RuleUpdater {
if (update.isChangeSeverity()) {
updateSeverity(update, rule);
}
+ if (update.isChangeImpacts()) {
+ updateImpactSeverityAndStandardSeverityIfTypeMatch(update, rule);
+ }
if (update.isChangeStatus()) {
updateStatus(update, rule);
}
@@ -129,7 +138,24 @@ public class RuleUpdater {
.stream()
.filter(i -> i.getSoftwareQuality().equals(ImpactMapper.convertToSoftwareQuality(rule.getEnumType())))
.findFirst()
- .ifPresent(i -> i.setSeverity(ImpactMapper.convertToImpactSeverity(severity)));
+ .ifPresent(i -> i.setSeverity(mapImpactSeverity(severity)));
+ }
+
+ private static void updateImpactSeverityAndStandardSeverityIfTypeMatch(RuleUpdate update, RuleDto rule) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = update.getImpactSeverities();
+ if (impacts.isEmpty()) {
+ throw new IllegalArgumentException("Impacts are is missing");
+ }
+ impacts.forEach((key, value) -> rule.getDefaultImpacts()
+ .stream()
+ .filter(i -> i.getSoftwareQuality().equals(key))
+ .findFirst()
+ .ifPresent(i -> {
+ i.setSeverity(value);
+ if (Objects.equals(convertToRuleType(key), RuleType.valueOf(rule.getType()))) {
+ rule.setSeverity(convertToDeprecatedSeverity(value));
+ }
+ }));
}
private static void updateName(RuleUpdate update, RuleDto rule) {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java
index 1f289d2e169..4097700b240 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java
@@ -23,8 +23,12 @@ import com.google.common.base.Splitter;
import com.google.common.io.Resources;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
@@ -50,6 +54,7 @@ import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
+import static org.sonar.server.common.ParamParsingUtils.parseImpact;
import static org.sonar.server.rule.ws.CreateAction.KEY_MAXIMUM_LENGTH;
import static org.sonar.server.rule.ws.CreateAction.NAME_MAXIMUM_LENGTH;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -65,6 +70,7 @@ public class UpdateAction implements RulesWsAction {
public static final String PARAM_NAME = "name";
public static final String PARAM_DESCRIPTION = "markdownDescription";
public static final String PARAM_SEVERITY = "severity";
+ public static final String PARAM_IMPACTS = "impacts";
public static final String PARAM_STATUS = "status";
public static final String PARAMS = "params";
@@ -91,13 +97,14 @@ public class UpdateAction implements RulesWsAction {
.setDescription("Update an existing rule.<br>" +
"Requires the 'Administer Quality Profiles' permission")
.setChangelog(
- new Change("10.2", "The field 'severity' and 'type' in the response have been deprecated, use 'impacts' instead."),
+ new Change("10.8", String.format("Parameter %s was added.", PARAM_IMPACTS)),
+ new Change("10.8", String.format("The parameter '%s' is not deprecated anymore.", PARAM_SEVERITY)),
+ new Change("10.8", "The field 'severity' and 'type' in the response are not deprecated anymore."),
new Change("10.4", String.format("The parameter '%s' is deprecated.", PARAM_SEVERITY)),
new Change("10.4", "Updating a removed rule is now possible."),
- new Change("10.8", String.format("The parameter '%s' is not deprecated anymore.", PARAM_SEVERITY)))
- .setSince("4.4")
- .setChangelog(
+ new Change("10.2", "The field 'severity' and 'type' in the response have been deprecated, use 'impacts' instead."),
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"))
+ .setSince("4.4")
.setHandler(this);
action.createParam(PARAM_KEY)
@@ -149,6 +156,11 @@ public class UpdateAction implements RulesWsAction {
.setPossibleValues(Severity.ALL);
action
+ .createParam(PARAM_IMPACTS)
+ .setDescription("Rule impacts, semicolon-separated (Only when updating a custom rule impact severity)")
+ .setExampleValue("MAINTAINABILITY=HIGH;SECURITY=LOW");
+
+ action
.createParam(PARAM_STATUS)
.setPossibleValues(RuleStatus.values())
.setDescription("Rule status (Only when updating a custom rule)");
@@ -186,6 +198,14 @@ public class UpdateAction implements RulesWsAction {
update.setMarkdownDescription(description);
}
String severity = request.param(PARAM_SEVERITY);
+ String impacts = request.param(PARAM_IMPACTS);
+ if (impacts != null && severity != null) {
+ throw new IllegalArgumentException("Both 'severity' and 'impacts' parameters cannot be set at the same time");
+ }
+ if (impacts != null) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parsedImpact = parseImpacts(impacts);
+ update.setImpactSeverities(parsedImpact);
+ }
if (severity != null) {
update.setSeverity(severity);
}
@@ -263,4 +283,13 @@ public class UpdateAction implements RulesWsAction {
return responseBuilder.build();
}
+
+ private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parseImpacts(String impacts) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parsedImpacts = new EnumMap<>(SoftwareQuality.class);
+ for (String impact : impacts.split(";")) {
+ Pair<SoftwareQuality, org.sonar.api.issue.impact.Severity> pair = parseImpact(impact);
+ parsedImpacts.put(pair.getKey(), pair.getValue());
+ }
+ return parsedImpacts;
+ }
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/common/ParamParsingUtilsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/common/ParamParsingUtilsTest.java
new file mode 100644
index 00000000000..494cd3400dc
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/common/ParamParsingUtilsTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.common;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ParamParsingUtilsTest {
+
+ @Test
+ void parseImpact_whenCorrectParam_ShouldReturnExpectedResult() {
+ Pair<SoftwareQuality, Severity> result = ParamParsingUtils.parseImpact("MAINTAINABILITY=BLOCKER");
+ assertEquals(SoftwareQuality.MAINTAINABILITY, result.getKey());
+ assertEquals(Severity.BLOCKER, result.getValue());
+ }
+
+ @Test
+ void parseImpact_whenInvalidParam_ShouldThrowException() {
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("MAINTAINABILITY"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid impact format: MAINTAINABILITY");
+ }
+
+ @Test
+ void parseImpact_whenInvalidValues_ShouldThrowException() {
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("MAINTAINABILITY=MAJOR"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No enum constant org.sonar.api.issue.impact.Severity.MAJOR");
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("BUG=LOW"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No enum constant org.sonar.api.issue.impact.SoftwareQuality.BUG");
+ }
+}
+