Browse Source

SONAR-19050 added characteristic to elasticsearch index

tags/10.1.0.73491
lukasz-jarocki-sonarsource 1 year ago
parent
commit
5b132ad8ee
39 changed files with 477 additions and 38 deletions
  1. 5
    3
      server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java
  2. 1
    6
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
  3. 1
    8
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
  4. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java
  5. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleTypeToRuleCharacteristicConverter.java
  6. 9
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java
  7. 4
    0
      server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java
  8. 2
    0
      server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
  9. 37
    1
      server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java
  10. 12
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
  11. 15
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
  12. 2
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
  13. 11
    4
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
  14. 11
    0
      server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java
  15. 17
    1
      server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java
  16. 2
    0
      server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java
  17. 12
    0
      server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java
  18. 3
    0
      server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java
  19. 2
    0
      server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java
  20. 7
    0
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  21. 13
    0
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
  22. 1
    0
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
  23. 76
    7
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
  24. 5
    4
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionIT.java
  25. 1
    0
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionIT.java
  26. 50
    1
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java
  27. 25
    0
      server/sonar-webserver-webapi/src/it/resources/org/sonar/server/issue/ws/SearchActionIT/result_for_characteristics_search.json
  28. 14
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  29. 2
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
  30. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
  31. 3
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java
  32. 9
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java
  33. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java
  34. 22
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java
  35. 70
    0
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/RuleMapperTest.java
  36. 1
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
  37. 12
    0
      sonar-ws/src/main/protobuf/ws-commons.proto
  38. 3
    0
      sonar-ws/src/main/protobuf/ws-issues.proto
  39. 1
    0
      sonar-ws/src/main/protobuf/ws-rules.proto

+ 5
- 3
server/sonar-db-dao/src/it/java/org/sonar/db/rule/RuleDaoIT.java View File

@@ -37,6 +37,7 @@ import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleQuery;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
@@ -457,7 +458,7 @@ public class RuleDaoIT {
.setSystemTags(newHashSet("systag1", "systag2"))
.setSecurityStandards(newHashSet("owaspTop10:a1", "cwe:123"))
.setType(RuleType.BUG)
.setCharacteristic(CLEAR)
.setCharacteristic(RuleCharacteristic.TESTED)
.setScope(Scope.ALL)
.setCreatedAt(1_500_000_000_000L)
.setUpdatedAt(2_000_000_000_000L);
@@ -485,8 +486,7 @@ public class RuleDaoIT {
assertThat(ruleDto.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123");
assertThat(ruleDto.getScope()).isEqualTo(Scope.ALL);
assertThat(ruleDto.getType()).isEqualTo(RuleType.BUG.getDbConstant());
assertThat(ruleDto.getCharacteristic()).isEqualTo(CLEAR);

assertThat(ruleDto.getCharacteristic()).isEqualTo(RuleCharacteristic.TESTED);
assertThat(ruleDto.getCreatedAt()).isEqualTo(1_500_000_000_000L);
assertThat(ruleDto.getUpdatedAt()).isEqualTo(2_000_000_000_000L);
assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
@@ -667,6 +667,8 @@ public class RuleDaoIT {
assertThat(ruleDto.getGapDescription()).isEqualTo(rule.getGapDescription());
assertThat(ruleDto.getSystemTags()).containsAll(rule.getSystemTags());
assertThat(ruleDto.getType()).isEqualTo(rule.getType());
assertThat(ruleDto.getCharacteristic()).isEqualTo(rule.getCharacteristic());

}

@Test

+ 1
- 6
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java View File

@@ -728,12 +728,7 @@ public final class IssueDto implements Serializable {

@CheckForNull
public RuleCharacteristic getEffectiveRuleCharacteristic() {
return ruleCharacteristic != null ? ruleCharacteristic : convertTypeToCharacteristic(ruleType);
}

private static RuleCharacteristic convertTypeToCharacteristic(int type) {
RuleType ruleType = RuleType.valueOf(type);
return convertToRuleCharacteristic(ruleType);
return ruleCharacteristic != null ? ruleCharacteristic : convertToRuleCharacteristic(ruleType);
}

public int getRuleType() {

+ 1
- 8
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java View File

@@ -41,7 +41,6 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
import static org.sonar.db.rule.RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic;

public class RuleDto {

@@ -403,14 +402,8 @@ public class RuleDto {
return this.characteristic;
}

@CheckForNull
public RuleCharacteristic getEffectiveCharacteristic() {
return characteristic != null ? characteristic : convertTypeToCharacteristic(type);
}

private static RuleCharacteristic convertTypeToCharacteristic(int type) {
RuleType ruleType = RuleType.valueOf(type);
return convertToRuleCharacteristic(ruleType);
return characteristic != null ? characteristic : RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic(type);
}

public RuleDto setCharacteristic(RuleCharacteristic characteristic) {

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java View File

@@ -27,6 +27,7 @@ import java.util.Set;
import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;

public class RuleForIndexingDto {
@@ -53,6 +54,7 @@ public class RuleForIndexingDto {
private long createdAt;
private long updatedAt;
private Set<RuleDescriptionSectionDto> ruleDescriptionSectionsDtos = new HashSet<>();
private RuleCharacteristic characteristic;

@VisibleForTesting
public RuleForIndexingDto() {
@@ -79,6 +81,7 @@ public class RuleForIndexingDto {
ruleForIndexingDto.type = r.getType();
ruleForIndexingDto.createdAt = r.getCreatedAt();
ruleForIndexingDto.updatedAt = r.getUpdatedAt();
ruleForIndexingDto.characteristic = r.getEffectiveCharacteristic();
if (r.getRuleDescriptionSectionDtos() != null) {
ruleForIndexingDto.setRuleDescriptionSectionsDtos(Sets.newHashSet(r.getRuleDescriptionSectionDtos()));
}
@@ -205,4 +208,8 @@ public class RuleForIndexingDto {
public void setType(int type) {
this.type = type;
}

public RuleCharacteristic getCharacteristic() {
return characteristic;
}
}

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleTypeToRuleCharacteristicConverter.java View File

@@ -27,6 +27,13 @@ public class RuleTypeToRuleCharacteristicConverter {
private RuleTypeToRuleCharacteristicConverter() {
}

public static RuleCharacteristic convertToRuleCharacteristic(int ruleType) {
if (ruleType == 0) {
return RuleCharacteristic.CLEAR;
}
return convertToRuleCharacteristic(RuleType.valueOf(ruleType));
}

public static RuleCharacteristic convertToRuleCharacteristic(RuleType ruleType) {
return switch (ruleType) {
case BUG -> RuleCharacteristic.ROBUST;

+ 9
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java View File

@@ -209,4 +209,13 @@ public class RuleDtoTest {
assertThat(effectiveCharacteristic).isEqualTo(characteristic).isEqualTo(RuleCharacteristic.COMPLIANT);
}

@Test
public void getEffectiveCharacteristic_whenType0_shouldReturnClearCharacteristic() {
RuleDto rule = new RuleDto().setType(0);

RuleCharacteristic effectiveCharacteristic = rule.getEffectiveCharacteristic();

assertThat(effectiveCharacteristic).isEqualTo(RuleCharacteristic.CLEAR);
}

}

+ 4
- 0
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java View File

@@ -214,6 +214,10 @@ public class RuleTesting {
return rule -> rule.setType(type);
}

public static Consumer<RuleDto> setCharacteristic(RuleCharacteristic characteristic) {
return rule -> rule.setCharacteristic(characteristic);
}

public static Consumer<RuleDto> setIsExternal(boolean isExternal) {
return rule -> rule.setIsExternal(isExternal);
}

+ 2
- 0
server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java View File

@@ -27,6 +27,7 @@ import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
@@ -90,6 +91,7 @@ public class IssueIteratorFactoryIT {
assertThat(issue.getTags()).containsOnly("tag1", "tag2", "tag3");
assertThat(issue.effort().toMinutes()).isPositive();
assertThat(issue.type().getDbConstant()).isEqualTo(2);
assertThat(issue.characteristic()).isEqualTo(RuleCharacteristic.CLEAR);
}

@Test

+ 37
- 1
server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java View File

@@ -45,7 +45,7 @@ import org.sonar.server.es.SearchOptions;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.security.SecurityStandards;

import static com.google.common.collect.ImmutableSet.of;
import static java.util.Set.of;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -60,12 +60,16 @@ import static org.sonar.api.rule.Severity.CRITICAL;
import static org.sonar.api.rule.Severity.INFO;
import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR;
import static org.sonar.api.rules.RuleCharacteristic.CLEAR;
import static org.sonar.api.rules.RuleCharacteristic.COMPLIANT;
import static org.sonar.api.rules.RuleCharacteristic.SECURE;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
import static org.sonar.db.rule.RuleTesting.newRule;
import static org.sonar.db.rule.RuleTesting.setCharacteristic;
import static org.sonar.db.rule.RuleTesting.setCreatedAt;
import static org.sonar.db.rule.RuleTesting.setIsExternal;
import static org.sonar.db.rule.RuleTesting.setIsTemplate;
@@ -359,6 +363,38 @@ public class RuleIndexIT {
assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
}

@Test
public void search_by_characteristic() {
RuleDto secure = createRule(setCharacteristic(SECURE));
RuleDto compliant = createRule(setCharacteristic(COMPLIANT));
RuleDto clear = createRule(setCharacteristic(CLEAR));
index();

// find all
RuleQuery query = new RuleQuery();
assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);

// find secure
query = new RuleQuery().setCharacteristics(of(SECURE));
assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(secure.getUuid());

// find compliant
query = new RuleQuery().setCharacteristics(of(COMPLIANT));
assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(compliant.getUuid());

// find clear
query = new RuleQuery().setCharacteristics(of(CLEAR));
assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(clear.getUuid());

// empty list => no filter
query = new RuleQuery().setTypes(of());
assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);

// null list => no filter
query = new RuleQuery().setTypes(null);
assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);
}

@Test
public void search_by_is_template() {
RuleDto ruleNoTemplate = createRule(setIsTemplate(false));

+ 12
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java View File

@@ -60,6 +60,7 @@ public class SearchRequest {
private List<String> statuses;
private List<String> tags;
private Set<String> types;
private Set<String> characteristics;
private List<String> pciDss32;
private List<String> pciDss40;
private List<String> owaspTop10;
@@ -502,4 +503,15 @@ public class SearchRequest {
this.owaspAsvsLevel = owaspAsvsLevel;
return this;
}

@CheckForNull
public Collection<String> getCharacteristics() {
return characteristics;
}

public SearchRequest setCharacteristics(@Nullable Collection<String> characteristics) {
this.characteristics = characteristics == null ? null : Set.copyOf(characteristics);
return this;
}

}

+ 15
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java View File

@@ -26,6 +26,7 @@ import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.server.es.BaseDoc;
@@ -133,6 +134,10 @@ public class IssueDoc extends BaseDoc {
return RuleType.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_TYPE));
}

public RuleCharacteristic characteristic() {
return RuleCharacteristic.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC));
}

@CheckForNull
public Duration effort() {
Number effort = getNullableField(IssueIndexDefinition.FIELD_ISSUE_EFFORT);
@@ -266,6 +271,11 @@ public class IssueDoc extends BaseDoc {
return this;
}

public IssueDoc setCharacteristic(RuleCharacteristic characteristic) {
setField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC, characteristic.toString());
return this;
}

@CheckForNull
public Collection<String> getPciDss32() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32);
@@ -366,4 +376,9 @@ public class IssueDoc extends BaseDoc {
setField(IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE, b);
return this;
}

public IssueDoc setCharacteristic(String characteristic) {
setField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC, characteristic);
return this;
}
}

+ 2
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java View File

@@ -95,6 +95,7 @@ public class IssueIndexDefinition implements IndexDefinition {
public static final String FIELD_ISSUE_STATUS = "status";
public static final String FIELD_ISSUE_TAGS = "tags";
public static final String FIELD_ISSUE_TYPE = "type";
public static final String FIELD_ISSUE_CHARACTERISTIC = "characteristic";
public static final String FIELD_ISSUE_PCI_DSS_32 = "pciDss-3.2";
public static final String FIELD_ISSUE_PCI_DSS_40 = "pciDss-4.0";
public static final String FIELD_ISSUE_OWASP_ASVS_40 = "owaspAsvs-4.0";
@@ -166,6 +167,7 @@ public class IssueIndexDefinition implements IndexDefinition {
mapping.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_CHARACTERISTIC).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_32).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_40).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_ASVS_40).disableNorms().build();

+ 11
- 4
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java View File

@@ -45,6 +45,7 @@ import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.db.DatabaseUtils.getLong;
import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
import static org.sonar.db.rule.RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic;
import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;

/**
@@ -76,13 +77,15 @@ class IssueIteratorForSingleChunk implements IssueIterator {
"c.branch_uuid",
"pb.is_main",
"pb.project_uuid",

// column 22
"i.tags",

// column 21
"i.issue_type",
"r.security_standards",
"c.qualifier",
"n.uuid"
"n.uuid",
"r.characteristic",
"r.rule_type"
};

private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
@@ -216,7 +219,7 @@ class IssueIteratorForSingleChunk implements IssueIterator {
doc.setFilePath(filePath);
doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
String branchUuid = rs.getString(17);
boolean isMainBranch = rs.getBoolean( 18);
boolean isMainBranch = rs.getBoolean(18);
String projectUuid = rs.getString(19);
doc.setBranchUuid(branchUuid);
doc.setIsMainBranch(isMainBranch);
@@ -239,6 +242,10 @@ class IssueIteratorForSingleChunk implements IssueIterator {

doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(23)) ? IssueScope.TEST : IssueScope.MAIN);
doc.setIsNewCodeReference(!isNullOrEmpty(rs.getString(24)));

String characteristic = rs.getString(25);
doc.setCharacteristic(characteristic != null ? characteristic : convertToRuleCharacteristic(rs.getInt(26)).name());

return doc;
}


+ 11
- 0
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java View File

@@ -32,6 +32,7 @@ import javax.annotation.Nullable;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
@@ -272,6 +273,15 @@ public class RuleDoc extends BaseDoc {
return this;
}

private RuleDoc setCharacteristic(RuleCharacteristic characteristic) {
setField(RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC, characteristic.name());
return this;
}

public RuleCharacteristic characteristic() {
return RuleCharacteristic.valueOf(getField(RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC));
}

public long createdAt() {
return getField(RuleIndexDefinition.FIELD_RULE_CREATED_AT);
}
@@ -314,6 +324,7 @@ public class RuleDoc extends BaseDoc {
.setSeverity(dto.getSeverityAsString())
.setStatus(dto.getStatus().toString())
.setType(dto.getTypeAsRuleType())
.setCharacteristic(dto.getCharacteristic())
.setCreatedAt(dto.getCreatedAt())
.setTags(Sets.union(dto.getTags(), dto.getSystemTags()))
.setUpdatedAt(dto.getUpdatedAt())

+ 17
- 1
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java View File

@@ -52,6 +52,7 @@ import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
@@ -89,6 +90,7 @@ import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CREATED_AT;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CWE;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
@@ -126,6 +128,7 @@ public class RuleIndex {
public static final String FACET_ACTIVE_SEVERITIES = "active_severities";
public static final String FACET_STATUSES = "statuses";
public static final String FACET_TYPES = "types";
public static final String FACET_CHARACTERISTICS = "characteristics";
public static final String FACET_OLD_DEFAULT = "true";
public static final String FACET_CWE = "cwe";
/**
@@ -290,11 +293,18 @@ public class RuleIndex {

Collection<RuleType> types = query.getTypes();
if (isNotEmpty(types)) {
List<String> typeNames = types.stream().map(RuleType::toString).collect(MoreCollectors.toList());
List<String> typeNames = types.stream().map(RuleType::toString).toList();
filters.put(FIELD_RULE_TYPE,
QueryBuilders.termsQuery(FIELD_RULE_TYPE, typeNames));
}

Collection<RuleCharacteristic> characteristics = query.getCharacteristics();
if (isNotEmpty(characteristics)) {
List<String> characteristicNames = characteristics.stream().map(RuleCharacteristic::name).toList();
filters.put(FIELD_RULE_CHARACTERISTIC,
QueryBuilders.termsQuery(FIELD_RULE_CHARACTERISTIC, characteristicNames));
}

if (query.getAvailableSinceLong() != null) {
filters.put("availableSince", QueryBuilders.rangeQuery(FIELD_RULE_CREATED_AT)
.gte(query.getAvailableSinceLong()));
@@ -458,6 +468,12 @@ public class RuleIndex {
stickyFacetBuilder.buildStickyFacet(FIELD_RULE_TYPE, FACET_TYPES,
(types == null) ? (new String[0]) : types.toArray()));
}
if (options.getFacets().contains(FACET_CHARACTERISTICS)) {
Collection<RuleCharacteristic> characteristics = query.getCharacteristics();
aggregations.put(FACET_CHARACTERISTICS,
stickyFacetBuilder.buildStickyFacet(FIELD_RULE_CHARACTERISTIC, FACET_CHARACTERISTICS,
(characteristics == null) ? (new String[0]) : characteristics.toArray()));
}
if (options.getFacets().contains(FACET_REPOSITORIES) || options.getFacets().contains(FACET_OLD_DEFAULT)) {
Collection<String> repositories = query.getRepositories();
aggregations.put(FACET_REPOSITORIES,

+ 2
- 0
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java View File

@@ -59,6 +59,7 @@ public class RuleIndexDefinition implements IndexDefinition {
public static final String FIELD_RULE_IS_EXTERNAL = "isExternal";
public static final String FIELD_RULE_TEMPLATE_KEY = "templateKey";
public static final String FIELD_RULE_TYPE = "type";
public static final String FIELD_RULE_CHARACTERISTIC = "characteristic";
public static final String FIELD_RULE_CREATED_AT = "createdAt";
public static final String FIELD_RULE_UPDATED_AT = "updatedAt";
public static final String FIELD_RULE_CWE = "cwe";
@@ -139,6 +140,7 @@ public class RuleIndexDefinition implements IndexDefinition {
ruleMapping.keywordFieldBuilder(FIELD_RULE_TEMPLATE_KEY).disableNorms().build();

ruleMapping.keywordFieldBuilder(FIELD_RULE_TYPE).disableNorms().build();
ruleMapping.keywordFieldBuilder(FIELD_RULE_CHARACTERISTIC).disableNorms().build();

ruleMapping.createLongField(FIELD_RULE_CREATED_AT);
ruleMapping.createLongField(FIELD_RULE_UPDATED_AT);

+ 12
- 0
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java View File

@@ -25,6 +25,7 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.db.qualityprofile.QProfileDto;

@@ -40,6 +41,7 @@ public class RuleQuery {
private Collection<RuleStatus> statuses;
private Collection<String> tags;
private Collection<RuleType> types;
private Collection<RuleCharacteristic> characteristics;
private Boolean activation;
private QProfileDto profile;
private QProfileDto compareToQProfile;
@@ -174,6 +176,16 @@ public class RuleQuery {
return this;
}

@CheckForNull
public Collection<RuleCharacteristic> getCharacteristics() {
return characteristics;
}

public RuleQuery setCharacteristics(@Nullable Collection<RuleCharacteristic> characteristics) {
this.characteristics = characteristics;
return this;
}

@CheckForNull
public Collection<String> getInheritance() {
return inheritance;

+ 3
- 0
server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.issue;

import java.util.List;
import org.junit.Test;

import static java.util.Arrays.asList;
@@ -42,6 +43,7 @@ public class SearchRequestTest {
.setScopes(asList("MAIN", "TEST"))
.setLanguages(singletonList("xoo"))
.setTags(asList("tag1", "tag2"))
.setCharacteristics(singletonList("CLEAR"))
.setAssigned(true)
.setCreatedAfter("2013-04-16T09:08:24+0200")
.setCreatedBefore("2013-04-17T09:08:24+0200")
@@ -79,6 +81,7 @@ public class SearchRequestTest {
assertThat(underTest.getOwaspAsvsLevel()).isEqualTo(2);
assertThat(underTest.getPciDss32()).containsExactly("1", "4");
assertThat(underTest.getPciDss40()).containsExactly("3", "5");
assertThat(underTest.getCharacteristics()).containsOnly("CLEAR");
}

@Test

+ 2
- 0
server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java View File

@@ -25,6 +25,7 @@ import java.util.Date;
import java.util.HashMap;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
@@ -74,6 +75,7 @@ public class IssueDocTesting {
doc.setKey(Uuids.createFast());
doc.setRuleUuid(Uuids.createFast());
doc.setType(RuleType.CODE_SMELL);
doc.setCharacteristic(RuleCharacteristic.COMPLIANT);
doc.setAssigneeUuid("assignee_uuid_" + randomAlphabetic(26));
doc.setAuthorLogin("author_" + randomAlphabetic(5));
doc.setScope(IssueScope.MAIN);

+ 7
- 0
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

@@ -66,6 +66,7 @@ import org.elasticsearch.search.sort.FieldSortBuilder;
import org.joda.time.Duration;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
@@ -137,9 +138,11 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY
import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
import static org.sonar.server.issue.index.IssueIndex.Facet.TYPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.CHARACTERISTICS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CWE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH;
@@ -179,6 +182,7 @@ import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
@@ -241,6 +245,7 @@ public class IssueIndex {
// Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
CHARACTERISTICS(PARAM_CHARACTERISTICS, FIELD_ISSUE_CHARACTERISTIC, STICKY, RuleCharacteristic.values().length),
SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE),
@@ -441,6 +446,7 @@ public class IssueIndex {
filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
filters.addFilter(FIELD_ISSUE_CHARACTERISTIC, CHARACTERISTICS.getFilterScope(), createTermsFilter(FIELD_ISSUE_CHARACTERISTIC, query.characteristics()));
filters.addFilter(
FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
@@ -784,6 +790,7 @@ public class IssueIndex {
addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, CHARACTERISTICS, query.characteristics().toArray());

addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());

+ 13
- 0
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java View File

@@ -74,6 +74,7 @@ public class IssueQuery {
private final Collection<String> languages;
private final Collection<String> tags;
private final Collection<String> types;
private final Collection<String> characteristics;
private final Collection<String> owaspTop10;
private final Collection<String> pciDss32;
private final Collection<String> pciDss40;
@@ -117,6 +118,7 @@ public class IssueQuery {
this.languages = defaultCollection(builder.languages);
this.tags = defaultCollection(builder.tags);
this.types = defaultCollection(builder.types);
this.characteristics = defaultCollection(builder.characteristics);
this.pciDss32 = defaultCollection(builder.pciDss32);
this.pciDss40 = defaultCollection(builder.pciDss40);
this.owaspAsvs40 = defaultCollection(builder.owaspAsvs40);
@@ -212,6 +214,10 @@ public class IssueQuery {
return types;
}

public Collection<String> characteristics() {
return characteristics;
}

public Collection<String> pciDss32() {
return pciDss32;
}
@@ -346,6 +352,7 @@ public class IssueQuery {
private Collection<String> scopes;
private Collection<String> languages;
private Collection<String> tags;
private Collection<String> characteristics;
private Collection<String> types;
private Collection<String> pciDss32;
private Collection<String> pciDss40;
@@ -461,6 +468,11 @@ public class IssueQuery {
return this;
}

public Builder characteristics(@Nullable Collection<String> characteristics) {
this.characteristics = characteristics;
return this;
}

public Builder pciDss32(@Nullable Collection<String> o) {
this.pciDss32 = o;
return this;
@@ -607,6 +619,7 @@ public class IssueQuery {
this.newCodeOnReferenceByProjectUuids = newCodeOnReferenceByProjectUuids;
return this;
}

}

private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {

+ 1
- 0
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java View File

@@ -138,6 +138,7 @@ public class IssueQueryFactory {
.languages(request.getLanguages())
.tags(request.getTags())
.types(request.getTypes())
.characteristics(request.getCharacteristics())
.pciDss32(request.getPciDss32())
.pciDss40(request.getPciDss40())
.owaspAsvs40(request.getOwaspAsvs40())

+ 76
- 7
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java View File

@@ -36,7 +36,9 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.Durations;
@@ -93,6 +95,7 @@ import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
import static org.sonar.api.rules.RuleType.*;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.server.ws.WebService.Param.FACETS;
import static org.sonar.api.utils.DateUtils.formatDateTime;
@@ -116,6 +119,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_ASSIGN;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TAGS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
@@ -396,10 +400,11 @@ public class SearchActionIT {
indexIssues();
userSession.logIn(john);

ws.newRequest()
TestResponse response = ws.newRequest()
.setParam("additionalFields", "comments,users")
.execute()
.assertJson(this.getClass(), "issue_with_comments.json");
.execute();

response.assertJson(this.getClass(), "issue_with_comments.json");
}

@Test
@@ -485,6 +490,60 @@ public class SearchActionIT {
.assertJson(this.getClass(), "load_additional_fields_with_issue_admin_permission.json");
}

@Test
public void search_by_characteristic_when_characteristic_not_set() {
RuleDto rule1 = newIssueRule(XOO_X1, r -> r.setType(CODE_SMELL).setCharacteristic(null));
RuleDto rule2 = newIssueRule(XOO_X2, r -> r.setType(RuleType.BUG).setCharacteristic(null));
ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setLanguage("java"));
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));

db.issues().insertIssue(rule1, project, file);
db.issues().insertIssue(rule2, project, file);
session.commit();
indexIssues();

userSession.logIn("john")
.addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone
indexPermissions();

SearchWsResponse searchWsResponse = ws.newRequest()
.setParam(PARAM_CHARACTERISTICS, Common.RuleCharacteristic.CLEAR.name() + "," + Common.RuleCharacteristic.ROBUST.name())
.executeProtobuf(SearchWsResponse.class);

List<Issue> issuesList = searchWsResponse.getIssuesList();

assertThat(issuesList).hasSize(2);
assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.CLEAR)).hasSize(1);
assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.ROBUST)).hasSize(1);
}

@Test
public void search_by_characteristic_when_characteristic_set() {
RuleDto rule1 = newIssueRule(XOO_X1, r -> r.setCharacteristic(RuleCharacteristic.PORTABLE));
RuleDto rule2 = newIssueRule(XOO_X2, r -> r.setCharacteristic(RuleCharacteristic.TESTED));
ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setLanguage("java"));
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));

db.issues().insertIssue(rule1, project, file);
db.issues().insertIssue(rule2, project, file);
session.commit();
indexIssues();

userSession.logIn("john")
.addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone
indexPermissions();

SearchWsResponse searchWsResponse = ws.newRequest()
.setParam(PARAM_CHARACTERISTICS, RuleCharacteristic.PORTABLE.name() + "," + RuleCharacteristic.TESTED.name())
.executeProtobuf(SearchWsResponse.class);

List<Issue> issuesList = searchWsResponse.getIssuesList();

assertThat(issuesList).hasSize(2);
assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.TESTED)).hasSize(1);
assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.PORTABLE)).hasSize(1);
}

@Test
public void search_by_rule_key() {
RuleDto rule = newIssueRule();
@@ -1598,11 +1657,11 @@ public class SearchActionIT {
RuleDto hotspotRule = newHotspotRule();
db.issues().insertHotspot(hotspotRule, project, file);
insertIssues(i -> i.setType(RuleType.BUG), i -> i.setType(RuleType.VULNERABILITY),
i -> i.setType(RuleType.CODE_SMELL));
i -> i.setType(CODE_SMELL));
indexPermissionsAndIssues();

TestRequest request = ws.newRequest()
.setParam("types", RuleType.SECURITY_HOTSPOT.toString());
.setParam("types", SECURITY_HOTSPOT.toString());
assertThatThrownBy(request::execute)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
@@ -1753,7 +1812,7 @@ public class SearchActionIT {
"additionalFields", "asc", "assigned", "assignees", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt",
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly",
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0",
"owaspAsvsLevel", "owaspTop10",
"owaspAsvsLevel", "owaspTop10", "characteristics",
"owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod");

WebService.Param branch = def.param(PARAM_BRANCH);
@@ -1818,10 +1877,20 @@ public class SearchActionIT {
}

private RuleDto newIssueRule() {
RuleDto rule = newRule(XOO_X1, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
return newIssueRule(XOO_X1);
}

private RuleDto newIssueRule(RuleKey key) {
return newIssueRule(key, r -> {
});
}

private RuleDto newIssueRule(RuleKey key, Consumer<RuleDto> consumer) {
RuleDto rule = newRule(key, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
.setLanguage("xoo")
.setName("Rule name")
.setStatus(RuleStatus.READY);
consumer.accept(rule);
db.rules().insert(rule);
return rule;
}

+ 5
- 4
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionIT.java View File

@@ -54,12 +54,12 @@ public class ActivateRulesActionIT {
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();

private DbClient dbClient = db.getDbClient();
private final DbClient dbClient = db.getDbClient();
private final QProfileWsSupport wsSupport = new QProfileWsSupport(dbClient, userSession);
private RuleQueryFactory ruleQueryFactory = mock(RuleQueryFactory.class, Mockito.RETURNS_MOCKS);
private final RuleQueryFactory ruleQueryFactory = mock(RuleQueryFactory.class, Mockito.RETURNS_MOCKS);

private QProfileRules qProfileRules = mock(QProfileRules.class, Mockito.RETURNS_DEEP_STUBS);
private WsActionTester ws = new WsActionTester(new ActivateRulesAction(ruleQueryFactory, userSession, qProfileRules, wsSupport, dbClient));
private final QProfileRules qProfileRules = mock(QProfileRules.class, Mockito.RETURNS_DEEP_STUBS);
private final WsActionTester ws = new WsActionTester(new ActivateRulesAction(ruleQueryFactory, userSession, qProfileRules, wsSupport, dbClient));

@Test
public void define_bulk_activate_rule_action() {
@@ -68,6 +68,7 @@ public class ActivateRulesActionIT {
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder(
"types",
"characteristics",
"template_key",
"languages",
"is_template",

+ 1
- 0
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionIT.java View File

@@ -69,6 +69,7 @@ public class DeactivateRulesActionIT {
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder(
"types",
"characteristics",
"template_key",
"languages",
"is_template",

+ 50
- 1
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java View File

@@ -33,6 +33,7 @@ import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.ws.WebService;
@@ -95,6 +96,7 @@ import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
import static org.sonar.db.rule.RuleTesting.setSystemTags;
import static org.sonar.db.rule.RuleTesting.setTags;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_RULE_KEY;
@@ -144,7 +146,7 @@ public class SearchActionIT {
assertThat(def.since()).isEqualTo("4.4");
assertThat(def.isInternal()).isFalse();
assertThat(def.responseExampleAsString()).isNotEmpty();
assertThat(def.params()).hasSize(28);
assertThat(def.params()).hasSize(29);

WebService.Param compareToProfile = def.param("compareToProfile");
assertThat(compareToProfile.since()).isEqualTo("6.5");
@@ -321,6 +323,7 @@ public class SearchActionIT {
assertThat(result.getRepo()).isNotEmpty();
assertThat(result.getSeverity()).isNotEmpty();
assertThat(result.getType().name()).isEqualTo(RuleType.valueOf(rule.getType()).name());
assertThat(result.getCharacteristic().name()).isEqualTo(rule.getEffectiveCharacteristic().name());
}

@Test
@@ -336,6 +339,7 @@ public class SearchActionIT {
// mandatory fields
assertThat(result.getKey()).isEqualTo(rule.getKey().toString());
assertThat(result.getType().getNumber()).isEqualTo(rule.getType());
assertThat(result.getCharacteristic().name()).isEqualTo(rule.getEffectiveCharacteristic().name());

// selected fields
assertThat(result.getCreatedAt()).isNotEmpty();
@@ -397,6 +401,51 @@ public class SearchActionIT {
verify(request, rule1);
}

@Test
public void characteristics_shouldFilterOnCharacteristics() {
RuleDto rule1 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.ROBUST));
RuleDto rule2 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
indexRules();

Consumer<TestRequest> populator = r -> r
.setParam(PARAM_CHARACTERISTICS, RuleCharacteristic.ROBUST.name());

TestRequest request = ws.newRequest();
populator.accept(request);

List<Rule> rulesList = request.executeProtobuf(SearchResponse.class).getRulesList();

assertThat(rulesList).hasSize(1);
assertThat(rulesList.get(0).getKey()).isEqualTo(rule1.getKey().toString());
}


@Test
public void characteristics_shouldGroupCharacteristicsInTheFacets() {
RuleDto rule1 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.ROBUST));
RuleDto rule2 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
RuleDto rule3 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
indexRules();

SearchResponse result = ws.newRequest()
.setParam("facets", "characteristics")
.executeProtobuf(SearchResponse.class);

Common.Facet facets = result.getFacets().getFacets(0);

int valuesCount = facets.getValuesCount();
assertThat(valuesCount).isEqualTo(RuleCharacteristic.values().length);

List<Common.FacetValue> valuesList = facets.getValuesList();
Common.FacetValue compliantFacetValue = valuesList.get(0);
assertThat(compliantFacetValue.getVal()).isEqualTo(RuleCharacteristic.COMPLIANT.name());
assertThat(compliantFacetValue.getCount()).isEqualTo(2);

Common.FacetValue robustFacetValue = valuesList.get(1);
assertThat(robustFacetValue.getVal()).isEqualTo(RuleCharacteristic.ROBUST.name());
assertThat(robustFacetValue.getCount()).isEqualTo(1);
}

@Test
public void when_searching_for_several_tags_combine_them_with_OR() {
RuleDto bothTagsRule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag1", "tag2"));

+ 25
- 0
server/sonar-webserver-webapi/src/it/resources/org/sonar/server/issue/ws/SearchActionIT/result_for_characteristics_search.json View File

@@ -0,0 +1,25 @@
{
"total": 2,
"p": 1,
"ps": 100,
"paging": {
"pageIndex": 1,
"pageSize": 100,
"total": 2
},
"issues": [ {
"rule": "xoo:x1",
"component": "FILE_KEY",
"project": "PROJECT_KEY",
"flows": [],
"status": "OPEN",
"characteristic": "CONSISTENT"
}, {
"rule": "xoo:x1",
"component": "FILE_KEY",
"project": "PROJECT_KEY",
"flows": [],
"status": "OPEN",
"characteristic": "CLEAR"
}]
}

+ 14
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java View File

@@ -33,6 +33,7 @@ import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
@@ -96,6 +97,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNED;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
@@ -130,6 +132,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;

public class SearchAction implements IssuesWsAction {
private static final String LOGIN_MYSELF = "__me__";
private static final String COMMA_SEPERATED_PARAMS_FORMAT = "%s,%s";
private static final Set<String> ISSUE_SCOPES = Arrays.stream(IssueScope.values()).map(Enum::name).collect(Collectors.toSet());
private static final EnumSet<RuleType> ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT));

@@ -148,6 +151,7 @@ public class SearchAction implements IssuesWsAction {
PARAM_LANGUAGES,
PARAM_TAGS,
PARAM_TYPES,
PARAM_CHARACTERISTICS,
PARAM_PCI_DSS_32,
PARAM_PCI_DSS_40,
PARAM_OWASP_ASVS_40,
@@ -193,6 +197,7 @@ public class SearchAction implements IssuesWsAction {
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
new Change("10.1", "Code characteristic is now included in the response. New parameter 'characteristics' added."),
new Change("10.0", "Parameter 'sansTop25' is deprecated"),
new Change("10.0", "The value 'sansTop25' for the parameter 'facets' has been deprecated"),
new Change("10.0", format("Deprecated value 'ASSIGNEE' in parameter '%s' is dropped", Param.SORT)),
@@ -274,7 +279,12 @@ public class SearchAction implements IssuesWsAction {
.setDescription("Comma-separated list of types.")
.setSince("5.5")
.setPossibleValues(ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS)
.setExampleValue(format("%s,%s", RuleType.CODE_SMELL, RuleType.BUG));
.setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, RuleType.CODE_SMELL, RuleType.BUG));
action.createParam(PARAM_CHARACTERISTICS)
.setDescription("Comma-separated list of characteristics.")
.setSince("10.1")
.setPossibleValues(Arrays.stream(RuleCharacteristic.values()).map(Enum::name).toList())
.setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, RuleCharacteristic.CLEAR, RuleCharacteristic.COMPLIANT));
action.createParam(PARAM_OWASP_ASVS_LEVEL)
.setDescription("Level of OWASP ASVS categories.")
.setSince("9.7")
@@ -324,7 +334,7 @@ public class SearchAction implements IssuesWsAction {
action.createParam(PARAM_SCOPES)
.setDescription("Comma-separated list of scopes. Available since 8.5")
.setPossibleValues(IssueScope.MAIN.name(), IssueScope.TEST.name())
.setExampleValue(format("%s,%s", IssueScope.MAIN.name(), IssueScope.TEST.name()));
.setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, IssueScope.MAIN.name(), IssueScope.TEST.name()));
action.createParam(PARAM_LANGUAGES)
.setDescription("Comma-separated list of languages. Available since 4.4")
.setExampleValue("java,js");
@@ -489,6 +499,7 @@ public class SearchAction implements IssuesWsAction {
addMandatoryValuesToFacet(facets, PARAM_SCOPES, ISSUE_SCOPES);
addMandatoryValuesToFacet(facets, PARAM_LANGUAGES, request.getLanguages());
addMandatoryValuesToFacet(facets, PARAM_TAGS, request.getTags());
addMandatoryValuesToFacet(facets, PARAM_CHARACTERISTICS, request.getCharacteristics());

setTypesFacet(facets);

@@ -567,6 +578,7 @@ public class SearchAction implements IssuesWsAction {
.setSeverities(request.paramAsStrings(PARAM_SEVERITIES))
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setCharacteristics(request.paramAsStrings(PARAM_CHARACTERISTICS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))
.setPciDss32(request.paramAsStrings(PARAM_PCI_DSS_32))
.setPciDss40(request.paramAsStrings(PARAM_PCI_DSS_40))

+ 2
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java View File

@@ -160,6 +160,7 @@ public class SearchResponseFormat {
private void addMandatoryFieldsToIssueBuilder(Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) {
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
issueBuilder.setCharacteristic(Common.RuleCharacteristic.valueOf(dto.getEffectiveRuleCharacteristic().name()));

ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
issueBuilder.setComponent(component.getKey());
@@ -175,6 +176,7 @@ public class SearchResponseFormat {
if (dto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) {
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
}

ofNullable(data.getUserByUuid(dto.getAssigneeUuid())).ifPresent(assignee -> issueBuilder.setAssignee(assignee.getLogin()));
ofNullable(emptyToNull(dto.getResolution())).ifPresent(issueBuilder::setResolution);
issueBuilder.setStatus(dto.getStatus());

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java View File

@@ -117,6 +117,7 @@ public class RuleMapper {
// Mandatory fields
ruleResponse.setKey(ruleDto.getKey().toString());
ruleResponse.setType(Common.RuleType.forNumber(ruleDto.getType()));
ruleResponse.setCharacteristic(Common.RuleCharacteristic.valueOf(ruleDto.getEffectiveCharacteristic().name()));

// Optional fields
setName(ruleResponse, ruleDto, fieldsToReturn);

+ 3
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java View File

@@ -22,6 +22,7 @@ package org.sonar.server.rule.ws;
import java.util.Date;
import java.util.List;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.ws.Request;
@@ -36,6 +37,7 @@ import static org.sonar.server.rule.ws.EnumUtils.toEnums;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
@@ -98,6 +100,7 @@ public class RuleQueryFactory {
query.setIsTemplate(request.paramAsBoolean(PARAM_IS_TEMPLATE));
query.setTemplateKey(request.param(PARAM_TEMPLATE_KEY));
query.setTypes(toEnums(request.paramAsStrings(PARAM_TYPES), RuleType.class));
query.setCharacteristics(toEnums(request.paramAsStrings(PARAM_CHARACTERISTICS), RuleCharacteristic.class));
query.setKey(request.param(PARAM_RULE_KEY));
query.setCwe(request.paramAsStrings(PARAM_CWE));
query.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10));

+ 9
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleWsSupport.java View File

@@ -26,6 +26,7 @@ import java.util.Objects;
import java.util.Set;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.ws.WebService;
@@ -50,6 +51,7 @@ import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFIL
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
@@ -171,6 +173,13 @@ public class RuleWsSupport {
.setPossibleValues(RuleType.values())
.setExampleValue(RuleType.BUG);

action
.createParam(PARAM_CHARACTERISTICS)
.setSince("10.1")
.setDescription("Comma-separated list of characteristics. Returned rules match any of the characteristics (OR operator)")
.setPossibleValues(RuleCharacteristic.values())
.setExampleValue(RuleCharacteristic.CLEAR);

action
.createParam(PARAM_ACTIVATION)
.setDescription("Filter rules that are activated or deactivated on the selected Quality profile. Ignored if " +

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java View File

@@ -32,6 +32,7 @@ public class RulesWsParameters {
public static final String PARAM_LANGUAGES = "languages";
public static final String PARAM_TAGS = "tags";
public static final String PARAM_TYPES = "types";
public static final String PARAM_CHARACTERISTICS = "characteristics";
public static final String PARAM_CWE = "cwe";
public static final String PARAM_OWASP_TOP_10 = "owaspTop10";
public static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021";

+ 22
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java View File

@@ -25,6 +25,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -38,6 +39,7 @@ import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
@@ -67,6 +69,7 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE;
import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED;
import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.index.RuleIndex.FACET_CHARACTERISTICS;
import static org.sonar.server.rule.index.RuleIndex.FACET_CWE;
import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
import static org.sonar.server.rule.index.RuleIndex.FACET_OLD_DEFAULT;
@@ -82,6 +85,7 @@ import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEPRECATED_KEYS;
import static org.sonar.server.rule.ws.RulesWsParameters.OPTIONAL_FIELDS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_OWASP_TOP_10;
@@ -107,6 +111,7 @@ public class SearchAction implements RulesWsAction {
FACET_ACTIVE_SEVERITIES,
FACET_STATUSES,
FACET_TYPES,
FACET_CHARACTERISTICS,
FACET_OLD_DEFAULT,
FACET_CWE,
FACET_OWASP_TOP_10,
@@ -166,7 +171,8 @@ public class SearchAction implements RulesWsAction {
new Change("10.0", "The value 'debtRemFn' for the 'f' parameter has been deprecated, use 'remFn' instead"),
new Change("10.0", "The value 'defaultDebtRemFn' for the 'f' parameter has been deprecated, use 'defaultRemFn' instead"),
new Change("10.0", "The value 'sansTop25' for the parameter 'facets' has been deprecated"),
new Change("10.0", "Parameter 'sansTop25' is deprecated")
new Change("10.0", "Parameter 'sansTop25' is deprecated"),
new Change("10.1", "The field 'characteristic' has been added to the response. New parameter 'characteristics has been added.")
);

action.createParam(FACETS)
@@ -319,6 +325,7 @@ public class SearchAction implements RulesWsAction {
addMandatoryFacetValues(results, FACET_ACTIVE_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, FACET_TAGS, request.getTags());
addMandatoryFacetValues(results, FACET_TYPES, RuleType.names());
addMandatoryFacetValues(results, FACET_CHARACTERISTICS, Arrays.stream(RuleCharacteristic.values()).map(Enum::name).toList());
addMandatoryFacetValues(results, FACET_CWE, request.getCwe());
addMandatoryFacetValues(results, FACET_OWASP_TOP_10, request.getOwaspTop10());
addMandatoryFacetValues(results, FACET_OWASP_TOP_10_2021, request.getOwaspTop10For2021());
@@ -335,6 +342,7 @@ public class SearchAction implements RulesWsAction {
facetValuesByFacetKey.put(FACET_ACTIVE_SEVERITIES, request.getActiveSeverities());
facetValuesByFacetKey.put(FACET_TAGS, request.getTags());
facetValuesByFacetKey.put(FACET_TYPES, request.getTypes());
facetValuesByFacetKey.put(FACET_CHARACTERISTICS, request.getCharacteristics());
facetValuesByFacetKey.put(FACET_CWE, request.getCwe());
facetValuesByFacetKey.put(FACET_OWASP_TOP_10, request.getOwaspTop10());
facetValuesByFacetKey.put(FACET_OWASP_TOP_10_2021, request.getOwaspTop10For2021());
@@ -398,6 +406,7 @@ public class SearchAction implements RulesWsAction {
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(request.paramAsStrings(PARAM_TYPES))
.setCharacteristics(request.paramAsStrings(PARAM_CHARACTERISTICS))
.setCwe(request.paramAsStrings(PARAM_CWE))
.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10))
.setOwaspTop10For2021(request.paramAsStrings(PARAM_OWASP_TOP_10_2021))
@@ -485,6 +494,7 @@ public class SearchAction implements RulesWsAction {
private List<String> statuses;
private List<String> tags;
private List<String> types;
private List<String> characteristics;
private List<String> cwe;
private List<String> owaspTop10;
private List<String> owaspTop10For2021;
@@ -590,6 +600,16 @@ public class SearchAction implements RulesWsAction {
return types;
}

private SearchRequest setCharacteristics(@Nullable List<String> characteristics) {
this.characteristics = characteristics;
return this;
}

@CheckForNull
private List<String> getCharacteristics() {
return characteristics;
}

public List<String> getCwe() {
return cwe;
}
@@ -639,5 +659,6 @@ public class SearchAction implements RulesWsAction {
this.sonarsourceSecurity = sonarsourceSecurity;
return this;
}

}
}

+ 70
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/RuleMapperTest.java View File

@@ -0,0 +1,70 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.rule.ws;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.db.rule.RuleDto;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Rules;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

@RunWith(DataProviderRunner.class)
public class RuleMapperTest {

@Test
@UseDataProvider("pluginApiEnumsMappedToProtobufEnums")
public void toWsRule_shouldMapCharacteristicEnumToProtobuf(RuleCharacteristic pluginApiEnum, Common.RuleCharacteristic protoEnum) {
RuleMapper ruleMapper = new RuleMapper(mock(Languages.class), mock(), mock());

RuleDto ruleDto = new RuleDto();
ruleDto.setRuleKey(RuleKey.of("repo", "key"));
ruleDto.setScope(RuleDto.Scope.ALL);
ruleDto.setCharacteristic(pluginApiEnum);
Rules.Rule rule = ruleMapper.toWsRule(ruleDto, new SearchAction.SearchResult(), Set.of());

assertThat(rule.getCharacteristic()).isEqualTo(protoEnum);
}

@DataProvider
public static Object[][] pluginApiEnumsMappedToProtobufEnums() {
return new Object[][] {
{RuleCharacteristic.CLEAR, Common.RuleCharacteristic.CLEAR},
{RuleCharacteristic.TESTED, Common.RuleCharacteristic.TESTED},
{RuleCharacteristic.ROBUST, Common.RuleCharacteristic.ROBUST},
{RuleCharacteristic.SECURE, Common.RuleCharacteristic.SECURE},
{RuleCharacteristic.CONSISTENT, Common.RuleCharacteristic.CONSISTENT},
{RuleCharacteristic.COMPLIANT, Common.RuleCharacteristic.COMPLIANT},
{RuleCharacteristic.STRUCTURED, Common.RuleCharacteristic.STRUCTURED},
{RuleCharacteristic.PORTABLE, Common.RuleCharacteristic.PORTABLE}
};
}
}

+ 1
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java View File

@@ -79,6 +79,7 @@ public class IssuesWsParameters {
public static final String PARAM_LANGUAGES = "languages";
public static final String PARAM_TAGS = "tags";
public static final String PARAM_TYPES = "types";
public static final String PARAM_CHARACTERISTICS = "characteristics";
public static final String PARAM_OWASP_ASVS_LEVEL = "owaspAsvsLevel";
public static final String PARAM_PCI_DSS = "pciDss";
public static final String PARAM_PCI_DSS_32 = "pciDss-3.2";

+ 12
- 0
sonar-ws/src/main/protobuf/ws-commons.proto View File

@@ -177,6 +177,18 @@ enum RuleType {
SECURITY_HOTSPOT = 4;
}

enum RuleCharacteristic {
UNKNOWN_RULE_CHARACTERISTIC = 0;
CLEAR = 1;
CONSISTENT = 2;
STRUCTURED = 3;
TESTED = 4;
ROBUST = 5;
SECURE = 6;
PORTABLE = 7;
COMPLIANT = 8;
}

enum BranchType {
reserved 1, 2;


+ 3
- 0
sonar-ws/src/main/protobuf/ws-issues.proto View File

@@ -161,6 +161,7 @@ message Issue {
optional string ruleDescriptionContextKey = 37;

repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38;
optional sonarqube.ws.commons.RuleCharacteristic characteristic = 39;
}

message Transitions {
@@ -267,6 +268,7 @@ message IssueLite {
optional sonarqube.ws.commons.RuleType type = 6;
optional Location mainLocation = 7;
optional bool closed = 8;
optional sonarqube.ws.commons.RuleCharacteristic characteristic = 9;
}


@@ -287,6 +289,7 @@ message TaintVulnerabilityLite {
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
optional sonarqube.ws.commons.RuleCharacteristic characteristic = 12;
}

message Flow {

+ 1
- 0
sonar-ws/src/main/protobuf/ws-rules.proto View File

@@ -120,6 +120,7 @@ message Rule {
optional DeprecatedKeys deprecatedKeys = 48;
optional DescriptionSections descriptionSections = 49;
optional EducationPrinciples educationPrinciples = 50;
optional sonarqube.ws.commons.RuleCharacteristic characteristic = 51;

message DescriptionSections {
repeated DescriptionSection descriptionSections = 1;

Loading…
Cancel
Save