@@ -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 |
@@ -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() { |
@@ -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) { |
@@ -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; | |||
} | |||
} |
@@ -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; |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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 |
@@ -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)); |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); |
@@ -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; | |||
} | |||
@@ -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()) |
@@ -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, |
@@ -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); |
@@ -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; |
@@ -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 |
@@ -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); |
@@ -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()); |
@@ -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) { |
@@ -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()) |
@@ -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; | |||
} |
@@ -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", |
@@ -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", |
@@ -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")); |
@@ -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" | |||
}] | |||
} |
@@ -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)) |
@@ -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()); |
@@ -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); |
@@ -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)); |
@@ -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 " + |
@@ -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"; |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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} | |||
}; | |||
} | |||
} |
@@ -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"; |
@@ -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; | |||
@@ -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 { |
@@ -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; |