// 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; | ||||
} | } |
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 >= #{changedSince,jdbcType=BIGINT} | AND i.issue_update_date >= #{changedSince,jdbcType=BIGINT} | ||||
</if> | </if> | ||||
order by i.kee | |||||
</select> | </select> | ||||
<select id="selectRecentlyClosedIssues" resultType="string"> | <select id="selectRecentlyClosedIssues" resultType="string"> |
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); | |||||
} | } | ||||
} | } |
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) { |
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; | |||||
} | |||||
} | } |
*/ | */ | ||||
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 |
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); | ||||
} | } | ||||
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); |
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()) |
} | } | ||||
@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)); |
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()); |
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(); |
"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"), |
.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"), |
"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."), |
"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)), |
"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"), |
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))) |
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); |
.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"), |
.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"), |
"</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"), |
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); |
"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", |
"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, |
"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", |
"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", |
"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, |
# 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 { |
"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": [ | ||||
{ | { |
"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, |
"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, |
"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, |
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)) |
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"; |
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 |
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 { |