Просмотр исходного кода

SONAR-20021 Update Issues web apis with Clean Code Taxonomy

tags/10.2.0.77647
Jacek Poreda 10 месяцев назад
Родитель
Сommit
177da8d8b0
37 измененных файлов: 490 добавлений и 54 удалений
  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 Просмотреть файл

// nothing to do // nothing to do
} }


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

public String getUuid() { public String getUuid() {
return uuid; return uuid;
} }

+ 8
- 2
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml Просмотреть файл

i.component_uuid as component_uuid, i.component_uuid as component_uuid,
i.assignee as assigneeUuid, i.assignee as assigneeUuid,
u.login as assigneeLogin, 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> </sql>


<select id="selectByBranch" parameterType="map" resultType="Issue">
<select id="selectByBranch" parameterType="map" resultMap="issueResultMap" resultOrdered="true">
select select
<include refid="selectByBranchColumns"/> <include refid="selectByBranchColumns"/>
, p.path as filePath , p.path as filePath
inner join rules r on r.uuid = i.rule_uuid inner join rules r on r.uuid = i.rule_uuid
inner join components p on p.uuid=i.component_uuid inner join components p on p.uuid=i.component_uuid
left join users u on i.assignee = u.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 where
<if test="keys.size() > 0"> <if test="keys.size() > 0">
i.kee IN i.kee IN
<if test="changedSince != null"> <if test="changedSince != null">
AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT} AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
</if> </if>
order by i.kee
</select> </select>


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

+ 1
- 11
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java Просмотреть файл

import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test; import org.junit.Test;
import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.util.UuidFactoryFast; 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.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.core.util.Uuids; import org.sonar.core.util.Uuids;
import org.sonar.db.issue.ImpactDto; import org.sonar.db.issue.ImpactDto;


import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple; 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.RuleDto.ERROR_MESSAGE_SECTION_ALREADY_EXISTS;
import static org.sonar.db.rule.RuleTesting.newRule; import static org.sonar.db.rule.RuleTesting.newRule;


} }


public static ImpactDto newImpactDto(SoftwareQuality softwareQuality, Severity severity) { 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 Просмотреть файл

import org.elasticsearch.search.aggregations.bucket.filter.Filter; import org.elasticsearch.search.aggregations.bucket.filter.Filter;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.missing.Missing; 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.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Sum; import org.elasticsearch.search.aggregations.metrics.Sum;




private void processMultiBucketAggregation(MultiBucketsAggregation aggregation) { private void processMultiBucketAggregation(MultiBucketsAggregation aggregation) {
LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName()); 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) { public boolean contains(String facetName) {

+ 30
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java Просмотреть файл

private List<String> rules; private List<String> rules;
private String sort; private String sort;
private List<String> severities; private List<String> severities;
private List<String> impactSeverities;
private List<String> impactSoftwareQualities;
private List<String> cleanCodeAttributesCategories;
private List<String> statuses; private List<String> statuses;
private List<String> tags; private List<String> tags;
private Set<String> types; private Set<String> types;
this.codeVariants = codeVariants; this.codeVariants = codeVariants;
return this; 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 Просмотреть файл

*/ */
package org.sonar.server.issue; package org.sonar.server.issue;


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


import static java.util.Arrays.asList; import static java.util.Arrays.asList;
.setOwaspAsvsLevel(2) .setOwaspAsvsLevel(2)
.setPciDss32(asList("1", "4")) .setPciDss32(asList("1", "4"))
.setPciDss40(asList("3", "5")) .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.getIssues()).containsOnlyOnce("anIssueKey");
assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR"); assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR");
assertThat(underTest.getPciDss32()).containsExactly("1", "4"); assertThat(underTest.getPciDss32()).containsExactly("1", "4");
assertThat(underTest.getPciDss40()).containsExactly("3", "5"); assertThat(underTest.getPciDss40()).containsExactly("3", "5");
assertThat(underTest.getCodeVariants()).containsExactly("variant1", "variant2"); assertThat(underTest.getCodeVariants()).containsExactly("variant1", "variant2");
assertThat(underTest.getCleanCodeAttributesCategories()).containsExactly("ADAPTABLE");
assertThat(underTest.getImpactSeverities()).containsExactly("HIGH", "LOW");
assertThat(underTest.getImpactSoftwareQualities()).containsExactly("RELIABILITY", "SECURITY");
} }


@Test @Test

+ 15
- 13
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java Просмотреть файл

import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery; import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.filters; 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.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
import static org.sonar.server.issue.index.IssueIndex.Facet.CWE; 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.DIRECTORIES;
import static org.sonar.server.issue.index.IssueIndex.Facet.FILES; 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.LANGUAGES;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40; import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10; import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25; 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.SCOPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES; 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.SONARSOURCE_SECURITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES; 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.TAGS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT; 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_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_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_IS_MAIN_BRANCH;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY; import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE; import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE; 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;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE; 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_SQ_SECURITY_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS; import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS; import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE; 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_DIRECTORIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; 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_LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25; 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_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES; 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_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;


public enum Facet { public enum Facet {
SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()), 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), 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), 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()), STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
} }


private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) { 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; return;
} }


} }


private static void addImpactSeverityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) { 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; return;
} }


IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(), IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
NO_EXTRA_FILTER, NO_EXTRA_FILTER,
t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS) 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); esRequest.aggregation(aggregation);
} }



+ 1
- 1
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java Просмотреть файл

private final Boolean newCodeOnReference; private final Boolean newCodeOnReference;
private final Collection<String> newCodeOnReferenceByProjectUuids; private final Collection<String> newCodeOnReferenceByProjectUuids;
private final Collection<String> codeVariants; private final Collection<String> codeVariants;
private Collection<String> cleanCodeAttributesCategories;
private final Collection<String> cleanCodeAttributesCategories;


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

+ 3
- 0
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java Просмотреть файл

IssueQuery.Builder builder = IssueQuery.builder() IssueQuery.Builder builder = IssueQuery.builder()
.issueKeys(request.getIssues()) .issueKeys(request.getIssues())
.severities(request.getSeverities()) .severities(request.getSeverities())
.cleanCodeAttributesCategories(request.getCleanCodeAttributesCategories())
.impactSoftwareQualities(request.getImpactSoftwareQualities())
.impactSeverities(request.getImpactSeverities())
.statuses(request.getStatuses()) .statuses(request.getStatuses())
.resolutions(request.getResolutions()) .resolutions(request.getResolutions())
.resolved(request.getResolved()) .resolved(request.getResolved())

+ 11
- 11
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java Просмотреть файл

} }


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


newDoc("I4", project.uuid(), file).setImpacts(Map.of( newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW))); MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));


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


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


MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW))); 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())), 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("MAINTAINABILITY", 2L),
entry("RELIABILITY", 0L), entry("RELIABILITY", 0L),
entry("SECURITY", 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("MAINTAINABILITY", 0L),
entry("RELIABILITY", 1L), entry("RELIABILITY", 1L),
entry("SECURITY", 0L)); 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("MAINTAINABILITY", 1L),
entry("RELIABILITY", 1L), entry("RELIABILITY", 1L),
entry("SECURITY", 0L)); entry("SECURITY", 0L));


assertThatFacetHasOnly(IssueQuery.builder() assertThatFacetHasOnly(IssueQuery.builder()
.tags(singletonList("my-tag")) .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("MAINTAINABILITY", 1L),
entry("RELIABILITY", 0L), entry("RELIABILITY", 0L),
entry("SECURITY", 0L)); entry("SECURITY", 0L));
} }


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


newDoc("I4", project.uuid(), file).setImpacts(Map.of( newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW))); MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));


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


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


newDoc("I4", project.uuid(), file).setImpacts(Map.of( newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW))); 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("HIGH", 1L),
entry("MEDIUM", 0L), entry("MEDIUM", 0L),
entry("LOW", 2L)); entry("LOW", 2L));

+ 14
- 0
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java Просмотреть файл

import org.sonarqube.ws.Issues; import org.sonarqube.ws.Issues;


import static java.lang.String.format; import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; 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.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.sonar.api.web.UserRole.USER; import static org.sonar.api.web.UserRole.USER;
assertThat(taintLite.getRuleKey()).isEqualTo("javasecurity:S1000"); assertThat(taintLite.getRuleKey()).isEqualTo("javasecurity:S1000");
assertThat(taintLite.getType()).isEqualTo(Common.RuleType.forNumber(issueDto.getType())); assertThat(taintLite.getType()).isEqualTo(Common.RuleType.forNumber(issueDto.getType()));
assertThat(taintLite.getAssignedToSubscribedUser()).isTrue(); 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(); Issues.Location location = taintLite.getMainLocation();
assertThat(location.getMessage()).isEqualTo(issueDto.getMessage()); assertThat(location.getMessage()).isEqualTo(issueDto.getMessage());

+ 157
- 4
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java Просмотреть файл

import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Languages; import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ProjectData; import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto; import org.sonar.db.component.SnapshotDto;
import org.sonar.db.issue.ImpactDto;
import org.sonar.db.issue.IssueChangeDto; import org.sonar.db.issue.IssueChangeDto;
import org.sonar.db.issue.IssueDto; import org.sonar.db.issue.IssueDto;
import org.sonar.db.permission.GroupPermissionDto; import org.sonar.db.permission.GroupPermissionDto;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS; 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_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS; 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_IN_NEW_CODE_PERIOD;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
.getIssuesList() .getIssuesList()
.get(0) .get(0)
.getActions() .getActions()
.getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
.getActionsList())
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));


response = ws.newRequest() response = ws.newRequest()
.setParam(PARAM_ADDITIONAL_FIELDS, "actions") .setParam(PARAM_ADDITIONAL_FIELDS, "actions")
.getIssuesList() .getIssuesList()
.get(0) .get(0)
.getActions() .getActions()
.getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
.getActionsList())
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
} }


@Test @Test
.assertJson(this.getClass(), "search_by_variants_with_facets.json"); .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 @Test
public void issue_on_removed_file() { public void issue_on_removed_file() {
RuleDto rule = newIssueRule(); RuleDto rule = newIssueRule();
"additionalFields", "asc", "assigned", "assignees", "author", "components", "branch", "pullRequest", "createdAfter", "createdAt", "additionalFields", "asc", "assigned", "assignees", "author", "components", "branch", "pullRequest", "createdAfter", "createdAt",
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", "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", "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); WebService.Param branch = def.param(PARAM_BRANCH);
assertThat(branch.isInternal()).isFalse(); assertThat(branch.isInternal()).isFalse();

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java Просмотреть файл

"Requires authentication and the following permission: 'Browse' on the project of the specified issue.") "Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"), 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 Просмотреть файл

.setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project") .setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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"), 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 Просмотреть файл

"Requires authentication and the following permission: 'Browse' on the project of the specified issue.") "Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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."), 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 Просмотреть файл

"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.") "The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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)), 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 Просмотреть файл

"Requires authentication and the following permission: 'Browse' on the project of the specified issue.") "Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"), 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 Просмотреть файл

import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.lucene.search.TotalHits; import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity; import org.sonar.api.rule.Severity;
import org.sonar.api.rules.CleanCodeAttributeCategory;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Request;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES; 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_AUTHOR;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_BRANCH; 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_CODE_VARIANTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25; 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_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES; 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_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
PARAM_CWE, PARAM_CWE,
PARAM_CREATED_AT, PARAM_CREATED_AT,
PARAM_SONARSOURCE_SECURITY, 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 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 static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(PARAM_FILES, PARAM_DIRECTORIES);


private final UserSession userSession; private final UserSession userSession;
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.") + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .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.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.1", "Add the 'codeVariants' parameter, facet and response field"),
new Change("10.0", "Parameter 'sansTop25' is deprecated"), new Change("10.0", "Parameter 'sansTop25' is deprecated"),
.setDescription("Comma-separated list of severities") .setDescription("Comma-separated list of severities")
.setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL) .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
.setPossibleValues(Severity.ALL); .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) action.createParam(PARAM_STATUSES)
.setDescription("Comma-separated list of statuses") .setDescription("Comma-separated list of statuses")
.setExampleValue(STATUS_OPEN + "," + STATUS_REOPENED) .setExampleValue(STATUS_OPEN + "," + STATUS_REOPENED)
private void completeFacets(Facets facets, SearchRequest request, IssueQuery query) { private void completeFacets(Facets facets, SearchRequest request, IssueQuery query) {
addMandatoryValuesToFacet(facets, PARAM_SEVERITIES, Severity.ALL); addMandatoryValuesToFacet(facets, PARAM_SEVERITIES, Severity.ALL);
addMandatoryValuesToFacet(facets, PARAM_STATUSES, ISSUE_STATUSES); 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, PARAM_RESOLUTIONS, concat(singletonList(""), RESOLUTIONS));
addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids()); addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids());
addMandatoryValuesToFacet(facets, PARAM_FILES, query.files()); addMandatoryValuesToFacet(facets, PARAM_FILES, query.files());
addMandatoryValuesToFacet(facets, PARAM_CODE_VARIANTS, request.getCodeVariants()); 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) { private static void setTypesFacet(Facets facets) {
Map<String, Long> typeFacet = facets.get(PARAM_TYPES); Map<String, Long> typeFacet = facets.get(PARAM_TYPES);
if (typeFacet != null) { if (typeFacet != null) {
.setRules(request.paramAsStrings(PARAM_RULES)) .setRules(request.paramAsStrings(PARAM_RULES))
.setSort(request.param(Param.SORT)) .setSort(request.param(Param.SORT))
.setSeverities(request.paramAsStrings(PARAM_SEVERITIES)) .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)) .setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS)) .setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES))) .setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))

+ 16
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java Просмотреть файл

import org.sonar.api.resources.Language; import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages; import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration; import org.sonar.api.utils.Duration;
issueBuilder.setKey(dto.getKey()); issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType())); 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()); ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
issueBuilder.setComponent(component.getKey()); issueBuilder.setComponent(component.getKey());
setBranchOrPr(component, issueBuilder, data); setBranchOrPr(component, issueBuilder, data);

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java Просмотреть файл

.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("10.2", "This endpoint is now deprecated."), 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("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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"), 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 Просмотреть файл

.setDescription("Set tags on an issue. <br/>" + .setDescription("Set tags on an issue. <br/>" +
"Requires authentication and Browse permission on project") "Requires authentication and Browse permission on project")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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"), 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 Просмотреть файл

"</ul>") "</ul>")
.setSince("5.5") .setSince("5.5")
.setChangelog( .setChangelog(
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("10.2", "This endpoint is now deprecated."), new Change("10.2", "This endpoint is now deprecated."),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"), new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"), 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 Просмотреть файл

import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.server.ServerSide; import org.sonar.api.server.ServerSide;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
taintBuilder.setSeverity(Common.Severity.valueOf(issueDto.getSeverity())); taintBuilder.setSeverity(Common.Severity.valueOf(issueDto.getSeverity()));
} }
taintBuilder.setType(Common.RuleType.forNumber(issueDto.getType())); 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.setClosed(false);
taintBuilder.setMainLocation(locationBuilder.build()); taintBuilder.setMainLocation(locationBuilder.build());
issueDto.getOptionalRuleDescriptionContextKey().ifPresent(taintBuilder::setRuleDescriptionContextKey); issueDto.getOptionalRuleDescriptionContextKey().ifPresent(taintBuilder::setRuleDescriptionContextKey);

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/add_comment-example.json Просмотреть файл

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

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/assign-example.json Просмотреть файл

"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"severity": "MAJOR", "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", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78, "line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/delete_comment-example.json Просмотреть файл

"issue": { "issue": {
"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"severity": "MAJOR", "severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "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 Просмотреть файл

"issue": { "issue": {
"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"impacts": [
{
"softwareQuality": "SECURITY",
"severity": "HIGH"
}
],
"severity": "MAJOR", "severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "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 Просмотреть файл

"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"severity": "MAJOR", "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", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78, "line": 78,

+ 8
- 5
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/pull-taint-example.proto Просмотреть файл

# 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 { message TaintVulnerabilityPullQueryTimestamp {
required int64 queryTimestamp = 1; required int64 queryTimestamp = 1;
} }


message TaintLite {
message TaintVulnerabilityLite {
required string key = 1; required string key = 1;
optional int64 creationDate = 2; optional int64 creationDate = 2;
optional bool resolved = 3; optional bool resolved = 3;
optional string ruleKey = 4; 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 Location mainLocation = 7;
optional bool closed = 8; optional bool closed = 8;
optional Flow flows = 9;
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10; optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11; 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 { message Location {

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json Просмотреть файл

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

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_severity-example.json Просмотреть файл

"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"severity": "MAJOR", "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", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78, "line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_tags-example.json Просмотреть файл

"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"severity": "MAJOR", "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", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78, "line": 78,

+ 8
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/set_type-example.json Просмотреть файл

"key": "AVibidgv1LF0E-ru2DVv", "key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301", "rule": "squid:S2301",
"severity": "MAJOR", "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", "component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij", "project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78, "line": 78,

+ 12
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchResponseFormatFormatOperationTest.java Просмотреть файл

import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.sonar.api.resources.Languages; import org.sonar.api.resources.Languages;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.utils.Duration; import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations; import org.sonar.api.utils.Durations;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat; 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.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;


private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) { private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
assertThat(issue.getKey()).isEqualTo(issueDto.getKey()); 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.getType().getNumber()).isEqualTo(issueDto.getType());
assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey()); assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString()); assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable()); assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null)); assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
assertThat(new ArrayList<>(issue.getCodeVariantsList())).containsExactlyInAnyOrderElementsOf(issueDto.getCodeVariants()); 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 @Test
componentDto = component; componentDto = component;
issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component) issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component)
.setType(CODE_SMELL) .setType(CODE_SMELL)
.setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
.setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5)) .setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
.setAssigneeUuid(userDto.getUuid()) .setAssigneeUuid(userDto.getUuid())
.setResolution("resolution_" + randomAlphanumeric(5)) .setResolution("resolution_" + randomAlphanumeric(5))

+ 2
- 4
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java Просмотреть файл

public static final String PARAM_TYPE = "type"; public static final String PARAM_TYPE = "type";
public static final String PARAM_ISSUES = "issues"; public static final String PARAM_ISSUES = "issues";
public static final String PARAM_SEVERITIES = "severities"; 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_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories";
public static final String PARAM_STATUSES = "statuses"; public static final String PARAM_STATUSES = "statuses";
public static final String PARAM_RESOLUTIONS = "resolutions"; public static final String PARAM_RESOLUTIONS = "resolutions";

+ 41
- 0
sonar-ws/src/main/protobuf/ws-commons.proto Просмотреть файл

ALL = 2; 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 // Lines start at 1 and line offsets start at 0
message TextRange { message TextRange {
// Start line. Should never be absent // Start line. Should never be absent

+ 6
- 0
sonar-ws/src/main/protobuf/ws-issues.proto Просмотреть файл

repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38; repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38;


repeated string codeVariants = 39; 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 { message Transitions {
repeated Flow flows = 9; repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10; optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11; 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 { message Flow {

Загрузка…
Отмена
Сохранить