From ba08edfe10e04f65f956474f21802991ee5e3e75 Mon Sep 17 00:00:00 2001 From: OrlovAlexander Date: Wed, 13 Nov 2024 17:56:07 +0100 Subject: [PATCH] SONAR-23593 UpdateAction accepts impacts --- .../sonar/server/rule/ws/UpdateActionIT.java | 129 +++++++++++++++--- .../server/common/ParamParsingUtils.java | 39 ++++++ .../org/sonar/server/common/package-info.java | 23 ++++ .../server/issue/ws/SetSeverityAction.java | 10 +- .../org/sonar/server/rule/RuleUpdate.java | 26 +++- .../org/sonar/server/rule/RuleUpdater.java | 28 +++- .../sonar/server/rule/ws/UpdateAction.java | 37 ++++- .../server/common/ParamParsingUtilsTest.java | 56 ++++++++ 8 files changed, 317 insertions(+), 31 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/ParamParsingUtils.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/common/package-info.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/common/ParamParsingUtilsTest.java 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 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 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 impactSeverities = new EnumMap<>(SoftwareQuality.class); private RuleStatus status; private final Map parameters = new HashMap<>(); @@ -140,6 +145,21 @@ public class RuleUpdate { return this; } + public Map getImpactSeverities() { + return impactSeverities; + } + + /** + * Impacts to be updated (only for custom rules) + */ + public RuleUpdate setImpactSeverities(Map 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 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.
" + "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) @@ -148,6 +155,11 @@ public class UpdateAction implements RulesWsAction { .setDescription("Rule severity (Only when updating a custom rule)") .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()) @@ -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 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 parseImpacts(String impacts) { + Map parsedImpacts = new EnumMap<>(SoftwareQuality.class); + for (String impact : impacts.split(";")) { + Pair 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 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"); + } +} + -- 2.39.5