Browse Source

SONAR-20021 Update Issues web apis with Clean Code Taxonomy

tags/10.2.0.77647
Jacek Poreda 9 months ago
parent
commit
177da8d8b0
37 changed files with 490 additions and 54 deletions
  1. 6
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/ImpactDto.java
  2. 8
    2
      server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
  3. 1
    11
      server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java
  4. 11
    1
      server/sonar-server-common/src/main/java/org/sonar/server/es/Facets.java
  5. 30
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
  6. 8
    1
      server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java
  7. 15
    13
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  8. 1
    1
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
  9. 3
    0
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
  10. 11
    11
      server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
  11. 14
    0
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java
  12. 157
    4
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
  13. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
  14. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AssignAction.java
  15. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DeleteCommentAction.java
  16. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
  17. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/EditCommentAction.java
  18. 45
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  19. 16
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
  20. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
  21. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java
  22. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
  23. 15
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java
  24. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/add_comment-example.json
  25. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/assign-example.json
  26. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/delete_comment-example.json
  27. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/do_transition-example.json
  28. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/edit_comment-example.json
  29. 8
    5
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/pull-taint-example.proto
  30. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json
  31. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_severity-example.json
  32. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_tags-example.json
  33. 8
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_type-example.json
  34. 12
    0
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchResponseFormatFormatOperationTest.java
  35. 2
    4
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
  36. 41
    0
      sonar-ws/src/main/protobuf/ws-commons.proto
  37. 6
    0
      sonar-ws/src/main/protobuf/ws-issues.proto

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

@@ -33,6 +33,12 @@ public class ImpactDto implements Serializable {
// nothing to do
}

public ImpactDto(String uuid, SoftwareQuality softwareQuality, Severity severity) {
this.uuid = uuid;
this.softwareQuality = softwareQuality;
this.severity = severity;
}

public String getUuid() {
return uuid;
}

+ 8
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml View File

@@ -801,10 +801,13 @@
i.component_uuid as component_uuid,
i.assignee as assigneeUuid,
u.login as assigneeLogin,
i.rule_description_context_key as ruleDescriptionContextKey
i.rule_description_context_key as ruleDescriptionContextKey,
<include refid="issueImpactsColumns"/>
<include refid="ruleDefaultImpactsColumns"/>
r.clean_code_attribute as cleanCodeAttribute
</sql>

<select id="selectByBranch" parameterType="map" resultType="Issue">
<select id="selectByBranch" parameterType="map" resultMap="issueResultMap" resultOrdered="true">
select
<include refid="selectByBranchColumns"/>
, p.path as filePath
@@ -812,6 +815,8 @@
inner join rules r on r.uuid = i.rule_uuid
inner join components p on p.uuid=i.component_uuid
left join users u on i.assignee = u.uuid
left outer join issues_impacts ii on i.kee = ii.issue_key
left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
where
<if test="keys.size() > 0">
i.kee IN
@@ -822,6 +827,7 @@
<if test="changedSince != null">
AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
</if>
order by i.kee
</select>

<select id="selectRecentlyClosedIssues" resultType="string">

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

@@ -24,15 +24,11 @@ import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.api.rule.RuleScope;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.core.util.Uuids;
import org.sonar.db.issue.ImpactDto;

@@ -41,8 +37,6 @@ import static org.apache.commons.lang.StringUtils.repeat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.db.rule.RuleDto.ERROR_MESSAGE_SECTION_ALREADY_EXISTS;
import static org.sonar.db.rule.RuleTesting.newRule;

@@ -248,10 +242,6 @@ public class RuleDtoTest {
}

public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) {
return new ImpactDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setSoftwareQuality(softwareQuality)
.setSeverity(severity);
return new ImpactDto(UuidFactoryFast.getInstance().create(), softwareQuality, severity);
}

}

+ 11
- 1
server/sonar-server-common/src/main/java/org/sonar/server/es/Facets.java View File

@@ -38,6 +38,7 @@ import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.filter.Filter;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.missing.Missing;
import org.elasticsearch.search.aggregations.bucket.nested.ReverseNested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Sum;

@@ -176,7 +177,16 @@ public class Facets {

private void processMultiBucketAggregation(MultiBucketsAggregation aggregation) {
LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName());
aggregation.getBuckets().forEach(bucket -> facet.put(bucket.getKeyAsString(), bucket.getDocCount()));
aggregation.getBuckets().forEach(bucket -> {
if (!bucket.getAggregations().asList().isEmpty()) {
Aggregation next = bucket.getAggregations().iterator().next();
if (next instanceof ReverseNested reverseNestedBucket) {
facet.put(bucket.getKeyAsString(), reverseNestedBucket.getDocCount());
}
} else {
facet.put(bucket.getKeyAsString(), bucket.getDocCount());
}
});
}

public boolean contains(String facetName) {

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

@@ -57,6 +57,9 @@ public class SearchRequest {
private List<String> rules;
private String sort;
private List<String> severities;
private List<String> impactSeverities;
private List<String> impactSoftwareQualities;
private List<String> cleanCodeAttributesCategories;
private List<String> statuses;
private List<String> tags;
private Set<String> types;
@@ -512,4 +515,31 @@ public class SearchRequest {
this.codeVariants = codeVariants;
return this;
}

public List<String> getImpactSeverities() {
return impactSeverities;
}

public SearchRequest setImpactSeverities(@Nullable List<String> impactSeverities) {
this.impactSeverities = impactSeverities;
return this;
}

public List<String> getImpactSoftwareQualities() {
return impactSoftwareQualities;
}

public SearchRequest setImpactSoftwareQualities(@Nullable List<String> impactSoftwareQualities) {
this.impactSoftwareQualities = impactSoftwareQualities;
return this;
}

public List<String> getCleanCodeAttributesCategories() {
return cleanCodeAttributesCategories;
}

public SearchRequest setCleanCodeAttributesCategories(@Nullable List<String> cleanCodeAttributesCategories) {
this.cleanCodeAttributesCategories = cleanCodeAttributesCategories;
return this;
}
}

+ 8
- 1
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;
@@ -54,7 +55,10 @@ public class SearchRequestTest {
.setOwaspAsvsLevel(2)
.setPciDss32(asList("1", "4"))
.setPciDss40(asList("3", "5"))
.setCodeVariants(asList("variant1", "variant2"));
.setCodeVariants(asList("variant1", "variant2"))
.setCleanCodeAttributesCategories(singletonList("ADAPTABLE"))
.setImpactSeverities(List.of("HIGH", "LOW"))
.setImpactSoftwareQualities(List.of("RELIABILITY", "SECURITY"));

assertThat(underTest.getIssues()).containsOnlyOnce("anIssueKey");
assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR");
@@ -81,6 +85,9 @@ public class SearchRequestTest {
assertThat(underTest.getPciDss32()).containsExactly("1", "4");
assertThat(underTest.getPciDss40()).containsExactly("3", "5");
assertThat(underTest.getCodeVariants()).containsExactly("variant1", "variant2");
assertThat(underTest.getCleanCodeAttributesCategories()).containsExactly("ADAPTABLE");
assertThat(underTest.getImpactSeverities()).containsExactly("HIGH", "LOW");
assertThat(underTest.getImpactSoftwareQualities()).containsExactly("RELIABILITY", "SECURITY");
}

@Test

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

@@ -111,6 +111,7 @@ import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
@@ -128,6 +129,8 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT;
import static org.sonar.server.issue.index.IssueIndex.Facet.CWE;
import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES;
import static org.sonar.server.issue.index.IssueIndex.Facet.FILES;
import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
@@ -140,8 +143,6 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
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;
@@ -159,6 +160,9 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FILE
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE;
@@ -176,9 +180,6 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
@@ -199,6 +200,8 @@ 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;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
@@ -210,8 +213,6 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES_SEVERTIIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
@@ -255,8 +256,8 @@ public class IssueIndex {

public enum Facet {
SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
IMPACT_SOFTWARE_QUALITY(PARAM_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
IMPACT_SEVERITY(PARAM_SOFTWARE_QUALITIES_SEVERTIIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
org.sonar.api.issue.impact.Severity.values().length),
CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length),
STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
@@ -900,7 +901,7 @@ public class IssueIndex {
}

private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
if (!options.getFacets().contains(PARAM_SOFTWARE_QUALITIES)) {
if (!options.getFacets().contains(PARAM_IMPACT_SOFTWARE_QUALITIES)) {
return;
}

@@ -924,7 +925,7 @@ public class IssueIndex {
}

private static void addImpactSeverityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
if (!options.getFacets().contains(PARAM_SOFTWARE_QUALITIES_SEVERTIIES)) {
if (!options.getFacets().contains(PARAM_IMPACT_SEVERITIES)) {
return;
}

@@ -942,8 +943,9 @@ public class IssueIndex {
IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
NO_EXTRA_FILTER,
t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS)
.subAggregation(filters(IMPACT_SEVERITY.getName(),
keyedFilters))));
.subAggregation(filters(IMPACT_SEVERITY.getName(), keyedFilters)
// we want to count the number of issues for each severity, so we need to reverse the nested aggregation
.subAggregation(reverseNested("reverse_nested_" + IMPACT_SOFTWARE_QUALITY.getName())))));
esRequest.aggregation(aggregation);
}


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

@@ -101,7 +101,7 @@ public class IssueQuery {
private final Boolean newCodeOnReference;
private final Collection<String> newCodeOnReferenceByProjectUuids;
private final Collection<String> codeVariants;
private Collection<String> cleanCodeAttributesCategories;
private final Collection<String> cleanCodeAttributesCategories;

private IssueQuery(Builder builder) {
this.issueKeys = defaultCollection(builder.issueKeys);

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

@@ -122,6 +122,9 @@ public class IssueQueryFactory {
IssueQuery.Builder builder = IssueQuery.builder()
.issueKeys(request.getIssues())
.severities(request.getSeverities())
.cleanCodeAttributesCategories(request.getCleanCodeAttributesCategories())
.impactSoftwareQualities(request.getImpactSoftwareQualities())
.impactSeverities(request.getImpactSeverities())
.statuses(request.getStatuses())
.resolutions(request.getResolutions())
.resolved(request.getResolved())

+ 11
- 11
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java View File

@@ -671,7 +671,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
}

@Test
public void search_shouldReturnSoftwareQualityFacet() {
public void search_shouldReturnImpactSoftwareQualitiesFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);

@@ -686,14 +686,14 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));

assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualities",
assertThatFacetHasOnly(IssueQuery.builder(), "impactSoftwareQualities",
entry("MAINTAINABILITY", 3L),
entry("RELIABILITY", 2L),
entry("SECURITY", 0L));
}

@Test
public void search_whenFilteredOnSeverity_shouldReturnSoftwareQualityFacet() {
public void search_whenFilteredOnSeverity_shouldReturnImpactSoftwareQualitiesFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);

@@ -710,31 +710,31 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));

assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())).impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())),
"softwareQualities",
"impactSoftwareQualities",
entry("MAINTAINABILITY", 2L),
entry("RELIABILITY", 0L),
entry("SECURITY", 0L));

assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "softwareQualities",
assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 0L),
entry("RELIABILITY", 1L),
entry("SECURITY", 0L));

assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 1L),
entry("RELIABILITY", 1L),
entry("SECURITY", 0L));

assertThatFacetHasOnly(IssueQuery.builder()
.tags(singletonList("my-tag"))
.impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
.impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 1L),
entry("RELIABILITY", 0L),
entry("SECURITY", 0L));
}

@Test
public void search_shouldReturnSoftwareQualitySeverityFacet() {
public void search_shouldReturnImpactSeverityFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);

@@ -749,14 +749,14 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));

assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualitiesSeverities",
assertThatFacetHasOnly(IssueQuery.builder(), "impactSeverities",
entry("HIGH", 2L),
entry("MEDIUM", 1L),
entry("LOW", 2L));
}

@Test
public void search_whenFilteredOnSoftwareQuality_shouldReturnSoftwareQualitySeverityFacet() {
public void search_whenFilteredOnSoftwareQuality_shouldReturnImpactSeverityFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);

@@ -771,7 +771,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));

assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "softwareQualitiesSeverities",
assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "impactSeverities",
entry("HIGH", 1L),
entry("MEDIUM", 0L),
entry("LOW", 2L));

+ 14
- 0
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java View File

@@ -56,9 +56,11 @@ import org.sonarqube.ws.Common;
import org.sonarqube.ws.Issues;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.web.UserRole.USER;
@@ -253,6 +255,18 @@ public class PullTaintActionIT {
assertThat(taintLite.getRuleKey()).isEqualTo("javasecurity:S1000");
assertThat(taintLite.getType()).isEqualTo(Common.RuleType.forNumber(issueDto.getType()));
assertThat(taintLite.getAssignedToSubscribedUser()).isTrue();
assertThat(taintLite.getCleanCodeAttribute())
.isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
assertThat(taintLite.getCleanCodeAttributeCategory())
.isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));

assertThat(taintLite.getImpactsList())
.extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
.containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
.entrySet()
.stream()
.map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
.collect(toList()));

Issues.Location location = taintLite.getMainLocation();
assertThat(location.getMessage()).isEqualTo(issueDto.getMessage());

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

@@ -26,6 +26,7 @@ import java.time.Clock;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
@@ -35,6 +36,7 @@ import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
@@ -51,6 +53,7 @@ import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.permission.GroupPermissionDto;
@@ -123,6 +126,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIAN
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
@@ -227,7 +232,8 @@ public class SearchActionIT {
.getIssuesList()
.get(0)
.getActions()
.getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
.getActionsList())
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));

response = ws.newRequest()
.setParam(PARAM_ADDITIONAL_FIELDS, "actions")
@@ -239,7 +245,8 @@ public class SearchActionIT {
.getIssuesList()
.get(0)
.getActions()
.getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
.getActionsList())
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
}

@Test
@@ -582,6 +589,152 @@ public class SearchActionIT {
.assertJson(this.getClass(), "search_by_variants_with_facets.json");
}

@Test
public void search_whenImpactSoftwareQualitiesFacetRequested_shouldReturnFacet() {
RuleDto rule = newIssueRule();
ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH))
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)));
indexPermissionsAndIssues();

SearchWsResponse response = ws.newRequest()
.setParam(FACETS, PARAM_IMPACT_SOFTWARE_QUALITIES)
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getIssuesList())
.extracting(Issue::getKey)
.containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey());

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SOFTWARE_QUALITIES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple("MAINTAINABILITY", 3L),
tuple("RELIABILITY", 3L),
tuple("SECURITY", 2L));
}

@Test
public void search_whenFilteredByImpactSeverities_shouldReturnImpactSoftwareQualitiesFacet() {
RuleDto rule = newIssueRule();
ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH))
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))
.addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)));
indexPermissionsAndIssues();

SearchWsResponse response = ws.newRequest()
.setParam(PARAM_IMPACT_SEVERITIES, org.sonar.api.issue.impact.Severity.LOW.name())
.setParam(FACETS, PARAM_IMPACT_SOFTWARE_QUALITIES)
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getIssuesList())
.extracting(Issue::getKey)
.containsExactlyInAnyOrder(issue3.getKey())
.doesNotContain(issue1.getKey(), issue2.getKey());

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SOFTWARE_QUALITIES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple("MAINTAINABILITY", 0L),
tuple("RELIABILITY", 1L),
tuple("SECURITY", 0L));
}

@Test
public void search_whenImpactSeveritiesFacetRequested_shouldReturnFacet() {
RuleDto rule = newIssueRule();
ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH),
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW),
new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM),
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))));
indexPermissionsAndIssues();

SearchWsResponse response = ws.newRequest()
.setParam(FACETS, PARAM_IMPACT_SEVERITIES)
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getIssuesList())
.extracting(Issue::getKey)
.containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey());

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple("HIGH", 2L),
tuple("MEDIUM", 1L),
tuple("LOW", 1L));
}

@Test
public void search_whenFilteredByImpactSoftwareQualities_shouldReturnFacet() {
RuleDto rule = newIssueRule();
ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH),
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
new ImpactDto(uuidFactory.create(), SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW),
new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM),
new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))));
indexPermissionsAndIssues();

SearchWsResponse response = ws.newRequest()
.setParam(PARAM_IMPACT_SOFTWARE_QUALITIES, SoftwareQuality.SECURITY.name())
.setParam(FACETS, PARAM_IMPACT_SEVERITIES)
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getIssuesList())
.extracting(Issue::getKey)
.containsExactlyInAnyOrder(issue1.getKey(), issue3.getKey())
.doesNotContain(issue2.getKey());

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple("HIGH", 1L),
tuple("MEDIUM", 1L),
tuple("LOW", 0L));
}

@Test
public void issue_on_removed_file() {
RuleDto rule = newIssueRule();
@@ -1790,8 +1943,8 @@ public class SearchActionIT {
"additionalFields", "asc", "assigned", "assignees", "author", "components", "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",
"owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants");
"owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants",
"cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities");

WebService.Param branch = def.param(PARAM_BRANCH);
assertThat(branch.isInternal()).isFalse();

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

@@ -72,6 +72,7 @@ public class AddCommentAction implements IssuesWsAction {
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"),

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

@@ -75,6 +75,7 @@ public class AssignAction implements IssuesWsAction {
.setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project")
.setSince("3.6")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),

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

@@ -60,6 +60,7 @@ public class DeleteCommentAction implements IssuesWsAction {
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."),

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

@@ -82,6 +82,7 @@ public class DoTransitionAction implements IssuesWsAction {
"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("8.1", format("transitions '%s' and '%s' are no more supported", SET_AS_IN_REVIEW, OPEN_AS_VULNERABILITY)),

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

@@ -66,6 +66,7 @@ public class EditCommentAction implements IssuesWsAction {
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"),

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

@@ -22,6 +22,7 @@ package org.sonar.server.issue.ws;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@@ -32,7 +33,9 @@ import javax.annotation.Nullable;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.CleanCodeAttributeCategory;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
@@ -94,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_CLEAN_CODE_ATTRIBUTE_CATEGORIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIANTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
@@ -122,6 +126,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
@@ -157,10 +163,15 @@ public class SearchAction implements IssuesWsAction {
PARAM_CWE,
PARAM_CREATED_AT,
PARAM_SONARSOURCE_SECURITY,
PARAM_CODE_VARIANTS
PARAM_CODE_VARIANTS,
PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES,
PARAM_IMPACT_SOFTWARE_QUALITIES,
PARAM_IMPACT_SEVERITIES
);

private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. ";
private static final String NEW_FACET_ADDED_MESSAGE = "Facet '%s' has been added";
private static final String NEW_PARAM_ADDED_MESSAGE = "Param '%s' has been added";
private static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(PARAM_FILES, PARAM_DIRECTORIES);

private final UserSession userSession;
@@ -194,6 +205,13 @@ 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.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_IMPACT_SOFTWARE_QUALITIES)),
new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_IMPACT_SEVERITIES)),
new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)),
new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_IMPACT_SOFTWARE_QUALITIES)),
new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_IMPACT_SEVERITIES)),
new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)),
new Change("10.2", format("Parameter '%s' renamed to '%s'", PARAM_COMPONENT_KEYS, PARAM_COMPONENTS)),
new Change("10.1", "Add the 'codeVariants' parameter, facet and response field"),
new Change("10.0", "Parameter 'sansTop25' is deprecated"),
@@ -256,6 +274,21 @@ public class SearchAction implements IssuesWsAction {
.setDescription("Comma-separated list of severities")
.setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
.setPossibleValues(Severity.ALL);
action.createParam(PARAM_IMPACT_SOFTWARE_QUALITIES)
.setSince("10.2")
.setDescription("Comma-separated list of Software Qualities")
.setExampleValue(SoftwareQuality.MAINTAINABILITY + "," + SoftwareQuality.RELIABILITY)
.setPossibleValues(SoftwareQuality.values());
action.createParam(PARAM_IMPACT_SEVERITIES)
.setSince("10.2")
.setDescription("Comma-separated list of Software Quality Severities")
.setExampleValue(org.sonar.api.issue.impact.Severity.HIGH + "," + org.sonar.api.issue.impact.Severity.MEDIUM)
.setPossibleValues(org.sonar.api.issue.impact.Severity.values());
action.createParam(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)
.setSince("10.2")
.setDescription("Comma-separated list of Clean Code Attribute Categories")
.setExampleValue(CleanCodeAttributeCategory.ADAPTABLE + "," + CleanCodeAttributeCategory.INTENTIONAL)
.setPossibleValues(CleanCodeAttributeCategory.values());
action.createParam(PARAM_STATUSES)
.setDescription("Comma-separated list of statuses")
.setExampleValue(STATUS_OPEN + "," + STATUS_REOPENED)
@@ -482,6 +515,10 @@ public class SearchAction implements IssuesWsAction {
private void completeFacets(Facets facets, SearchRequest request, IssueQuery query) {
addMandatoryValuesToFacet(facets, PARAM_SEVERITIES, Severity.ALL);
addMandatoryValuesToFacet(facets, PARAM_STATUSES, ISSUE_STATUSES);
addMandatoryValuesToFacet(facets, PARAM_IMPACT_SOFTWARE_QUALITIES, enumToStringCollection(SoftwareQuality.values()));
addMandatoryValuesToFacet(facets, PARAM_IMPACT_SEVERITIES, enumToStringCollection(org.sonar.api.issue.impact.Severity.values()));
addMandatoryValuesToFacet(facets, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, enumToStringCollection(CleanCodeAttributeCategory.values()));

addMandatoryValuesToFacet(facets, PARAM_RESOLUTIONS, concat(singletonList(""), RESOLUTIONS));
addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids());
addMandatoryValuesToFacet(facets, PARAM_FILES, query.files());
@@ -512,6 +549,10 @@ public class SearchAction implements IssuesWsAction {
addMandatoryValuesToFacet(facets, PARAM_CODE_VARIANTS, request.getCodeVariants());
}

private static Collection<String> enumToStringCollection(Enum<?>... enumValues) {
return Arrays.stream(enumValues).map(Enum::name).toList();
}

private static void setTypesFacet(Facets facets) {
Map<String, Long> typeFacet = facets.get(PARAM_TYPES);
if (typeFacet != null) {
@@ -575,6 +616,9 @@ public class SearchAction implements IssuesWsAction {
.setRules(request.paramAsStrings(PARAM_RULES))
.setSort(request.param(Param.SORT))
.setSeverities(request.paramAsStrings(PARAM_SEVERITIES))
.setImpactSeverities(request.paramAsStrings(PARAM_IMPACT_SEVERITIES))
.setImpactSoftwareQualities(request.paramAsStrings(PARAM_IMPACT_SOFTWARE_QUALITIES))
.setCleanCodeAttributesCategories(request.paramAsStrings(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES))
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))

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

@@ -31,6 +31,7 @@ import java.util.stream.Collectors;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration;
@@ -173,6 +174,21 @@ public class SearchResponseFormat {
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));

CleanCodeAttribute cleanCodeAttribute = dto.getCleanCodeAttribute();
String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
if (cleanCodeAttributeString != null) {
issueBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttributeString));
issueBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttributeCategoryString));
}
issueBuilder.addAllImpacts(dto.getEffectiveImpacts().entrySet()
.stream()
.map(entry -> Common.Impact.newBuilder()
.setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name()))
.setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name()))
.build())
.toList());

ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
issueBuilder.setComponent(component.getKey());
setBranchOrPr(component, issueBuilder, data);

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

@@ -80,6 +80,7 @@ public class SetSeverityAction implements IssuesWsAction {
.setSince("3.6")
.setChangelog(
new Change("10.2", "This endpoint is now deprecated."),
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),

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

@@ -74,6 +74,7 @@ public class SetTagsAction implements IssuesWsAction {
.setDescription("Set tags on an issue. <br/>" +
"Requires authentication and Browse permission on project")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),

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

@@ -83,6 +83,7 @@ public class SetTypeAction implements IssuesWsAction {
"</ul>")
.setSince("5.5")
.setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("10.2", "This endpoint is now deprecated."),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),

+ 15
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java View File

@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.server.ServerSide;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -95,6 +96,20 @@ public class PullTaintActionProtobufObjectGenerator implements ProtobufObjectGen
taintBuilder.setSeverity(Common.Severity.valueOf(issueDto.getSeverity()));
}
taintBuilder.setType(Common.RuleType.forNumber(issueDto.getType()));
CleanCodeAttribute cleanCodeAttribute = issueDto.getCleanCodeAttribute();
String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
if (cleanCodeAttributeString != null) {
taintBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttributeString));
taintBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttributeCategoryString));
}
taintBuilder.addAllImpacts(issueDto.getEffectiveImpacts().entrySet()
.stream().map(entry -> Common.Impact.newBuilder()
.setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name()))
.setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name()))
.build())
.toList());

taintBuilder.setClosed(false);
taintBuilder.setMainLocation(locationBuilder.build());
issueDto.getOptionalRuleDescriptionContextKey().ifPresent(taintBuilder::setRuleDescriptionContextKey);

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/add_comment-example.json View File

@@ -2,6 +2,14 @@
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/assign-example.json View File

@@ -3,6 +3,14 @@
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/delete_comment-example.json View File

@@ -2,6 +2,14 @@
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/do_transition-example.json View File

@@ -2,6 +2,14 @@
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/edit_comment-example.json View File

@@ -3,6 +3,14 @@
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,

+ 8
- 5
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/pull-taint-example.proto View File

@@ -1,20 +1,23 @@
# The response contains a single protocol buffer message: TaintVulnerabilityPullQueryTimestamp followed by 0..n number of TaintLite protocol buffer messages.
# The response contains a single protocol buffer message: TaintVulnerabilityPullQueryTimestamp followed by 0..n number of TaintVulnerabilityLite protocol buffer messages.
message TaintVulnerabilityPullQueryTimestamp {
required int64 queryTimestamp = 1;
}

message TaintLite {
message TaintVulnerabilityLite {
required string key = 1;
optional int64 creationDate = 2;
optional bool resolved = 3;
optional string ruleKey = 4;
optional string severity = 5;
optional string type = 6;
optional sonarqube.ws.commons.Severity severity = 5;
optional sonarqube.ws.commons.RuleType type = 6;
optional Location mainLocation = 7;
optional bool closed = 8;
optional Flow flows = 9;
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 12;
optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 13;
repeated sonarqube.ws.commons.Impact impacts = 14;
}

message Location {

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json View File

@@ -13,6 +13,14 @@
"status": "RESOLVED",
"resolution": "WONTFIX",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"message": "Remove this unused private \"getKee\" method.",
"messageFormattings": [
{

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_severity-example.json View File

@@ -3,6 +3,14 @@
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_tags-example.json View File

@@ -3,6 +3,14 @@
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_type-example.json View File

@@ -3,6 +3,14 @@
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,

+ 12
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchResponseFormatFormatOperationTest.java View File

@@ -30,6 +30,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.sonar.api.resources.Languages;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.db.DbTester;
@@ -51,6 +52,7 @@ import static java.lang.System.currentTimeMillis;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -122,6 +124,8 @@ public class SearchResponseFormatFormatOperationTest {

private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));
assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
@@ -140,6 +144,13 @@ public class SearchResponseFormatFormatOperationTest {
assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
assertThat(new ArrayList<>(issue.getCodeVariantsList())).containsExactlyInAnyOrderElementsOf(issueDto.getCodeVariants());
assertThat(issue.getImpactsList())
.extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
.containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
.entrySet()
.stream()
.map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
.collect(toList()));
}

@Test
@@ -295,6 +306,7 @@ public class SearchResponseFormatFormatOperationTest {
componentDto = component;
issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component)
.setType(CODE_SMELL)
.setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
.setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
.setAssigneeUuid(userDto.getUuid())
.setResolution("resolution_" + randomAlphanumeric(5))

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

@@ -54,10 +54,8 @@ public class IssuesWsParameters {
public static final String PARAM_TYPE = "type";
public static final String PARAM_ISSUES = "issues";
public static final String PARAM_SEVERITIES = "severities";
public static final String PARAM_SOFTWARE_QUALITIES = "softwareQualities";

//TODO: To be discussed for the naming
public static final String PARAM_SOFTWARE_QUALITIES_SEVERTIIES = "softwareQualitiesSeverities";
public static final String PARAM_IMPACT_SOFTWARE_QUALITIES = "impactSoftwareQualities";
public static final String PARAM_IMPACT_SEVERITIES = "impactSeverities";
public static final String PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories";
public static final String PARAM_STATUSES = "statuses";
public static final String PARAM_RESOLUTIONS = "resolutions";

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

@@ -78,6 +78,47 @@ enum RuleScope {
ALL = 2;
}

enum CleanCodeAttribute {
CONVENTIONAL = 0;
FORMATTED = 1;
IDENTIFIABLE = 2;
CLEAR = 3;
COMPLETE = 4;
EFFICIENT = 5;
LOGICAL = 6;
DISTINCT = 7;
FOCUSED = 8;
MODULAR = 9;
TESTED = 10;
LAWFUL = 11;
RESPECTFUL = 12;
TRUSTWORTHY = 13;
}

enum CleanCodeAttributeCategory {
ADAPTABLE = 0;
CONSISTENT = 1;
INTENTIONAL = 2;
RESPONSIBLE = 3;
}

message Impact {
required SoftwareQuality softwareQuality = 1;
required ImpactSeverity severity = 2;
}

enum SoftwareQuality {
MAINTAINABILITY = 0;
RELIABILITY = 1;
SECURITY = 2;
}

enum ImpactSeverity {
LOW = 0;
MEDIUM = 1;
HIGH = 2;
}

// Lines start at 1 and line offsets start at 0
message TextRange {
// Start line. Should never be absent

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

@@ -163,6 +163,9 @@ message Issue {
repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38;

repeated string codeVariants = 39;
optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 40;
optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 41;
repeated sonarqube.ws.commons.Impact impacts = 42;
}

message Transitions {
@@ -289,6 +292,9 @@ message TaintVulnerabilityLite {
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 12;
optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 13;
repeated sonarqube.ws.commons.Impact impacts = 14;
}

message Flow {

Loading…
Cancel
Save