// nothing to do
}
+ public ImpactDto(String uuid, SoftwareQuality softwareQuality, Severity severity) {
+ this.uuid = uuid;
+ this.softwareQuality = softwareQuality;
+ this.severity = severity;
+ }
+
public String getUuid() {
return uuid;
}
i.component_uuid as component_uuid,
i.assignee as assigneeUuid,
u.login as assigneeLogin,
- i.rule_description_context_key as ruleDescriptionContextKey
+ i.rule_description_context_key as ruleDescriptionContextKey,
+ <include refid="issueImpactsColumns"/>
+ <include refid="ruleDefaultImpactsColumns"/>
+ r.clean_code_attribute as cleanCodeAttribute
</sql>
- <select id="selectByBranch" parameterType="map" resultType="Issue">
+ <select id="selectByBranch" parameterType="map" resultMap="issueResultMap" resultOrdered="true">
select
<include refid="selectByBranchColumns"/>
, p.path as filePath
inner join rules r on r.uuid = i.rule_uuid
inner join components p on p.uuid=i.component_uuid
left join users u on i.assignee = u.uuid
+ left outer join issues_impacts ii on i.kee = ii.issue_key
+ left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
where
<if test="keys.size() > 0">
i.kee IN
<if test="changedSince != null">
AND i.issue_update_date >= #{changedSince,jdbcType=BIGINT}
</if>
+ order by i.kee
</select>
<select id="selectRecentlyClosedIssues" resultType="string">
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.api.rule.RuleScope;
-import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
-import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.core.util.Uuids;
import org.sonar.db.issue.ImpactDto;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import static org.sonar.db.rule.RuleDto.ERROR_MESSAGE_SECTION_ALREADY_EXISTS;
import static org.sonar.db.rule.RuleTesting.newRule;
}
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.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.missing.Missing;
+import org.elasticsearch.search.aggregations.bucket.nested.ReverseNested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Sum;
private void processMultiBucketAggregation(MultiBucketsAggregation aggregation) {
LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName());
- aggregation.getBuckets().forEach(bucket -> facet.put(bucket.getKeyAsString(), bucket.getDocCount()));
+ aggregation.getBuckets().forEach(bucket -> {
+ if (!bucket.getAggregations().asList().isEmpty()) {
+ Aggregation next = bucket.getAggregations().iterator().next();
+ if (next instanceof ReverseNested reverseNestedBucket) {
+ facet.put(bucket.getKeyAsString(), reverseNestedBucket.getDocCount());
+ }
+ } else {
+ facet.put(bucket.getKeyAsString(), bucket.getDocCount());
+ }
+ });
}
public boolean contains(String facetName) {
private List<String> rules;
private String sort;
private List<String> severities;
+ private List<String> impactSeverities;
+ private List<String> impactSoftwareQualities;
+ private List<String> cleanCodeAttributesCategories;
private List<String> statuses;
private List<String> tags;
private Set<String> types;
this.codeVariants = codeVariants;
return this;
}
+
+ public List<String> getImpactSeverities() {
+ return impactSeverities;
+ }
+
+ public SearchRequest setImpactSeverities(@Nullable List<String> impactSeverities) {
+ this.impactSeverities = impactSeverities;
+ return this;
+ }
+
+ public List<String> getImpactSoftwareQualities() {
+ return impactSoftwareQualities;
+ }
+
+ public SearchRequest setImpactSoftwareQualities(@Nullable List<String> impactSoftwareQualities) {
+ this.impactSoftwareQualities = impactSoftwareQualities;
+ return this;
+ }
+
+ public List<String> getCleanCodeAttributesCategories() {
+ return cleanCodeAttributesCategories;
+ }
+
+ public SearchRequest setCleanCodeAttributesCategories(@Nullable List<String> cleanCodeAttributesCategories) {
+ this.cleanCodeAttributesCategories = cleanCodeAttributesCategories;
+ return this;
+ }
}
*/
package org.sonar.server.issue;
+import java.util.List;
import org.junit.Test;
import static java.util.Arrays.asList;
.setOwaspAsvsLevel(2)
.setPciDss32(asList("1", "4"))
.setPciDss40(asList("3", "5"))
- .setCodeVariants(asList("variant1", "variant2"));
+ .setCodeVariants(asList("variant1", "variant2"))
+ .setCleanCodeAttributesCategories(singletonList("ADAPTABLE"))
+ .setImpactSeverities(List.of("HIGH", "LOW"))
+ .setImpactSoftwareQualities(List.of("RELIABILITY", "SECURITY"));
assertThat(underTest.getIssues()).containsOnlyOnce("anIssueKey");
assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR");
assertThat(underTest.getPciDss32()).containsExactly("1", "4");
assertThat(underTest.getPciDss40()).containsExactly("3", "5");
assertThat(underTest.getCodeVariants()).containsExactly("variant1", "variant2");
+ assertThat(underTest.getCleanCodeAttributesCategories()).containsExactly("ADAPTABLE");
+ assertThat(underTest.getImpactSeverities()).containsExactly("HIGH", "LOW");
+ assertThat(underTest.getImpactSoftwareQualities()).containsExactly("RELIABILITY", "SECURITY");
}
@Test
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
+import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
import static org.sonar.server.issue.index.IssueIndex.Facet.CWE;
import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES;
import static org.sonar.server.issue.index.IssueIndex.Facet.FILES;
+import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
+import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
-import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
-import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY;
import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
-import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
-import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
-import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES;
-import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SOFTWARE_QUALITIES_SEVERTIIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
public enum Facet {
SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
- IMPACT_SOFTWARE_QUALITY(PARAM_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
- IMPACT_SEVERITY(PARAM_SOFTWARE_QUALITIES_SEVERTIIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
+ IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length),
+ IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY,
org.sonar.api.issue.impact.Severity.values().length),
CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length),
STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
}
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;
}
}
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;
}
IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
NO_EXTRA_FILTER,
t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS)
- .subAggregation(filters(IMPACT_SEVERITY.getName(),
- keyedFilters))));
+ .subAggregation(filters(IMPACT_SEVERITY.getName(), keyedFilters)
+ // we want to count the number of issues for each severity, so we need to reverse the nested aggregation
+ .subAggregation(reverseNested("reverse_nested_" + IMPACT_SOFTWARE_QUALITY.getName())))));
esRequest.aggregation(aggregation);
}
private final Boolean newCodeOnReference;
private final Collection<String> newCodeOnReferenceByProjectUuids;
private final Collection<String> codeVariants;
- private Collection<String> cleanCodeAttributesCategories;
+ private final Collection<String> cleanCodeAttributesCategories;
private IssueQuery(Builder builder) {
this.issueKeys = defaultCollection(builder.issueKeys);
IssueQuery.Builder builder = IssueQuery.builder()
.issueKeys(request.getIssues())
.severities(request.getSeverities())
+ .cleanCodeAttributesCategories(request.getCleanCodeAttributesCategories())
+ .impactSoftwareQualities(request.getImpactSoftwareQualities())
+ .impactSeverities(request.getImpactSeverities())
.statuses(request.getStatuses())
.resolutions(request.getResolutions())
.resolved(request.getResolved())
}
@Test
- public void search_shouldReturnSoftwareQualityFacet() {
+ public void search_shouldReturnImpactSoftwareQualitiesFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
- assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualities",
+ assertThatFacetHasOnly(IssueQuery.builder(), "impactSoftwareQualities",
entry("MAINTAINABILITY", 3L),
entry("RELIABILITY", 2L),
entry("SECURITY", 0L));
}
@Test
- public void search_whenFilteredOnSeverity_shouldReturnSoftwareQualityFacet() {
+ public void search_whenFilteredOnSeverity_shouldReturnImpactSoftwareQualitiesFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())).impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())),
- "softwareQualities",
+ "impactSoftwareQualities",
entry("MAINTAINABILITY", 2L),
entry("RELIABILITY", 0L),
entry("SECURITY", 0L));
- assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "softwareQualities",
+ assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 0L),
entry("RELIABILITY", 1L),
entry("SECURITY", 0L));
- assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
+ assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 1L),
entry("RELIABILITY", 1L),
entry("SECURITY", 0L));
assertThatFacetHasOnly(IssueQuery.builder()
.tags(singletonList("my-tag"))
- .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
+ .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "impactSoftwareQualities",
entry("MAINTAINABILITY", 1L),
entry("RELIABILITY", 0L),
entry("SECURITY", 0L));
}
@Test
- public void search_shouldReturnSoftwareQualitySeverityFacet() {
+ public void search_shouldReturnImpactSeverityFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
- assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualitiesSeverities",
+ assertThatFacetHasOnly(IssueQuery.builder(), "impactSeverities",
entry("HIGH", 2L),
entry("MEDIUM", 1L),
entry("LOW", 2L));
}
@Test
- public void search_whenFilteredOnSoftwareQuality_shouldReturnSoftwareQualitySeverityFacet() {
+ public void search_whenFilteredOnSoftwareQuality_shouldReturnImpactSeverityFacet() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project);
newDoc("I4", project.uuid(), file).setImpacts(Map.of(
MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
- assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "softwareQualitiesSeverities",
+ assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "impactSeverities",
entry("HIGH", 1L),
entry("MEDIUM", 0L),
entry("LOW", 2L));
import org.sonarqube.ws.Issues;
import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.web.UserRole.USER;
assertThat(taintLite.getRuleKey()).isEqualTo("javasecurity:S1000");
assertThat(taintLite.getType()).isEqualTo(Common.RuleType.forNumber(issueDto.getType()));
assertThat(taintLite.getAssignedToSubscribedUser()).isTrue();
+ assertThat(taintLite.getCleanCodeAttribute())
+ .isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
+ assertThat(taintLite.getCleanCodeAttributeCategory())
+ .isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));
+
+ assertThat(taintLite.getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
+ .entrySet()
+ .stream()
+ .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
+ .collect(toList()));
Issues.Location location = taintLite.getMainLocation();
assertThat(location.getMessage()).isEqualTo(issueDto.getMessage());
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.issue.ImpactDto;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.permission.GroupPermissionDto;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
.getIssuesList()
.get(0)
.getActions()
- .getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
+ .getActionsList())
+ .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
response = ws.newRequest()
.setParam(PARAM_ADDITIONAL_FIELDS, "actions")
.getIssuesList()
.get(0)
.getActions()
- .getActionsList()).isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
+ .getActionsList())
+ .isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY));
}
@Test
.assertJson(this.getClass(), "search_by_variants_with_facets.json");
}
+ @Test
+ public void search_whenImpactSoftwareQualitiesFacetRequested_shouldReturnFacet() {
+ RuleDto rule = newIssueRule();
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
+ c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+ IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH))
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
+ IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
+ IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam(FACETS, PARAM_IMPACT_SOFTWARE_QUALITIES)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList())
+ .extracting(Issue::getKey)
+ .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey());
+
+ Optional<Common.Facet> first = response.getFacets().getFacetsList()
+ .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SOFTWARE_QUALITIES))
+ .findFirst();
+ assertThat(first.get().getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactlyInAnyOrder(
+ tuple("MAINTAINABILITY", 3L),
+ tuple("RELIABILITY", 3L),
+ tuple("SECURITY", 2L));
+ }
+
+ @Test
+ public void search_whenFilteredByImpactSeverities_shouldReturnImpactSoftwareQualitiesFacet() {
+ RuleDto rule = newIssueRule();
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
+ c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+ IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH))
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
+ IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)));
+ IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))
+ .addImpact(new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam(PARAM_IMPACT_SEVERITIES, org.sonar.api.issue.impact.Severity.LOW.name())
+ .setParam(FACETS, PARAM_IMPACT_SOFTWARE_QUALITIES)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList())
+ .extracting(Issue::getKey)
+ .containsExactlyInAnyOrder(issue3.getKey())
+ .doesNotContain(issue1.getKey(), issue2.getKey());
+
+ Optional<Common.Facet> first = response.getFacets().getFacetsList()
+ .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SOFTWARE_QUALITIES))
+ .findFirst();
+ assertThat(first.get().getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactlyInAnyOrder(
+ tuple("MAINTAINABILITY", 0L),
+ tuple("RELIABILITY", 1L),
+ tuple("SECURITY", 0L));
+ }
+
+ @Test
+ public void search_whenImpactSeveritiesFacetRequested_shouldReturnFacet() {
+ RuleDto rule = newIssueRule();
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
+ c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+ IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+ IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+ IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam(FACETS, PARAM_IMPACT_SEVERITIES)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList())
+ .extracting(Issue::getKey)
+ .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey(), issue3.getKey());
+
+ Optional<Common.Facet> first = response.getFacets().getFacetsList()
+ .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES))
+ .findFirst();
+ assertThat(first.get().getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactlyInAnyOrder(
+ tuple("HIGH", 2L),
+ tuple("MEDIUM", 1L),
+ tuple("LOW", 1L));
+ }
+
+ @Test
+ public void search_whenFilteredByImpactSoftwareQualities_shouldReturnFacet() {
+ RuleDto rule = newIssueRule();
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
+ c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent();
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+ IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+ IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+ IssueDto issue3 = db.issues().insertIssue(rule, project, file, i -> i.replaceAllImpacts(List.of(
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ new ImpactDto(uuidFactory.create(), SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam(PARAM_IMPACT_SOFTWARE_QUALITIES, SoftwareQuality.SECURITY.name())
+ .setParam(FACETS, PARAM_IMPACT_SEVERITIES)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList())
+ .extracting(Issue::getKey)
+ .containsExactlyInAnyOrder(issue1.getKey(), issue3.getKey())
+ .doesNotContain(issue2.getKey());
+
+ Optional<Common.Facet> first = response.getFacets().getFacetsList()
+ .stream().filter(facet -> facet.getProperty().equals(PARAM_IMPACT_SEVERITIES))
+ .findFirst();
+ assertThat(first.get().getValuesList())
+ .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+ .containsExactlyInAnyOrder(
+ tuple("HIGH", 1L),
+ tuple("MEDIUM", 1L),
+ tuple("LOW", 0L));
+ }
+
@Test
public void issue_on_removed_file() {
RuleDto rule = newIssueRule();
"additionalFields", "asc", "assigned", "assignees", "author", "components", "branch", "pullRequest", "createdAfter", "createdAt",
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly",
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0",
- "owaspAsvsLevel", "owaspTop10",
- "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants");
+ "owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants",
+ "cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities");
WebService.Param branch = def.param(PARAM_BRANCH);
assertThat(branch.isInternal()).isFalse();
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"),
.setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."),
"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("8.1", format("transitions '%s' and '%s' are no more supported", SET_AS_IN_REVIEW, OPEN_AS_VULNERABILITY)),
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.3", "the response returns the issue with all its details"),
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.CleanCodeAttributeCategory;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_BRANCH;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIANTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
PARAM_CWE,
PARAM_CREATED_AT,
PARAM_SONARSOURCE_SECURITY,
- PARAM_CODE_VARIANTS
+ PARAM_CODE_VARIANTS,
+ PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES,
+ PARAM_IMPACT_SOFTWARE_QUALITIES,
+ PARAM_IMPACT_SEVERITIES
);
private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. ";
+ private static final String NEW_FACET_ADDED_MESSAGE = "Facet '%s' has been added";
+ private static final String NEW_PARAM_ADDED_MESSAGE = "Param '%s' has been added";
private static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(PARAM_FILES, PARAM_DIRECTORIES);
private final UserSession userSession;
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
+ new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_IMPACT_SOFTWARE_QUALITIES)),
+ new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_IMPACT_SEVERITIES)),
+ new Change("10.2", format(NEW_PARAM_ADDED_MESSAGE, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)),
+ new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_IMPACT_SOFTWARE_QUALITIES)),
+ new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_IMPACT_SEVERITIES)),
+ new Change("10.2", format(NEW_FACET_ADDED_MESSAGE, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)),
new Change("10.2", format("Parameter '%s' renamed to '%s'", PARAM_COMPONENT_KEYS, PARAM_COMPONENTS)),
new Change("10.1", "Add the 'codeVariants' parameter, facet and response field"),
new Change("10.0", "Parameter 'sansTop25' is deprecated"),
.setDescription("Comma-separated list of severities")
.setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
.setPossibleValues(Severity.ALL);
+ action.createParam(PARAM_IMPACT_SOFTWARE_QUALITIES)
+ .setSince("10.2")
+ .setDescription("Comma-separated list of Software Qualities")
+ .setExampleValue(SoftwareQuality.MAINTAINABILITY + "," + SoftwareQuality.RELIABILITY)
+ .setPossibleValues(SoftwareQuality.values());
+ action.createParam(PARAM_IMPACT_SEVERITIES)
+ .setSince("10.2")
+ .setDescription("Comma-separated list of Software Quality Severities")
+ .setExampleValue(org.sonar.api.issue.impact.Severity.HIGH + "," + org.sonar.api.issue.impact.Severity.MEDIUM)
+ .setPossibleValues(org.sonar.api.issue.impact.Severity.values());
+ action.createParam(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES)
+ .setSince("10.2")
+ .setDescription("Comma-separated list of Clean Code Attribute Categories")
+ .setExampleValue(CleanCodeAttributeCategory.ADAPTABLE + "," + CleanCodeAttributeCategory.INTENTIONAL)
+ .setPossibleValues(CleanCodeAttributeCategory.values());
action.createParam(PARAM_STATUSES)
.setDescription("Comma-separated list of statuses")
.setExampleValue(STATUS_OPEN + "," + STATUS_REOPENED)
private void completeFacets(Facets facets, SearchRequest request, IssueQuery query) {
addMandatoryValuesToFacet(facets, PARAM_SEVERITIES, Severity.ALL);
addMandatoryValuesToFacet(facets, PARAM_STATUSES, ISSUE_STATUSES);
+ addMandatoryValuesToFacet(facets, PARAM_IMPACT_SOFTWARE_QUALITIES, enumToStringCollection(SoftwareQuality.values()));
+ addMandatoryValuesToFacet(facets, PARAM_IMPACT_SEVERITIES, enumToStringCollection(org.sonar.api.issue.impact.Severity.values()));
+ addMandatoryValuesToFacet(facets, PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, enumToStringCollection(CleanCodeAttributeCategory.values()));
+
addMandatoryValuesToFacet(facets, PARAM_RESOLUTIONS, concat(singletonList(""), RESOLUTIONS));
addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids());
addMandatoryValuesToFacet(facets, PARAM_FILES, query.files());
addMandatoryValuesToFacet(facets, PARAM_CODE_VARIANTS, request.getCodeVariants());
}
+ private static Collection<String> enumToStringCollection(Enum<?>... enumValues) {
+ return Arrays.stream(enumValues).map(Enum::name).toList();
+ }
+
private static void setTypesFacet(Facets facets) {
Map<String, Long> typeFacet = facets.get(PARAM_TYPES);
if (typeFacet != null) {
.setRules(request.paramAsStrings(PARAM_RULES))
.setSort(request.param(Param.SORT))
.setSeverities(request.paramAsStrings(PARAM_SEVERITIES))
+ .setImpactSeverities(request.paramAsStrings(PARAM_IMPACT_SEVERITIES))
+ .setImpactSoftwareQualities(request.paramAsStrings(PARAM_IMPACT_SOFTWARE_QUALITIES))
+ .setCleanCodeAttributesCategories(request.paramAsStrings(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES))
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration;
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
+ CleanCodeAttribute cleanCodeAttribute = dto.getCleanCodeAttribute();
+ String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
+ String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
+ if (cleanCodeAttributeString != null) {
+ issueBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttributeString));
+ issueBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttributeCategoryString));
+ }
+ issueBuilder.addAllImpacts(dto.getEffectiveImpacts().entrySet()
+ .stream()
+ .map(entry -> Common.Impact.newBuilder()
+ .setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name()))
+ .setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name()))
+ .build())
+ .toList());
+
ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
issueBuilder.setComponent(component.getKey());
setBranchOrPr(component, issueBuilder, data);
.setSince("3.6")
.setChangelog(
new Change("10.2", "This endpoint is now deprecated."),
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),
.setDescription("Set tags on an issue. <br/>" +
"Requires authentication and Browse permission on project")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
new Change("6.5", "the database ids of the components are removed from the response"),
"</ul>")
.setSince("5.5")
.setChangelog(
+ new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
new Change("10.2", "This endpoint is now deprecated."),
new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.8", "The response field components.uuid is removed"),
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.server.ServerSide;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
taintBuilder.setSeverity(Common.Severity.valueOf(issueDto.getSeverity()));
}
taintBuilder.setType(Common.RuleType.forNumber(issueDto.getType()));
+ CleanCodeAttribute cleanCodeAttribute = issueDto.getCleanCodeAttribute();
+ String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
+ String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
+ if (cleanCodeAttributeString != null) {
+ taintBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttributeString));
+ taintBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttributeCategoryString));
+ }
+ taintBuilder.addAllImpacts(issueDto.getEffectiveImpacts().entrySet()
+ .stream().map(entry -> Common.Impact.newBuilder()
+ .setSoftwareQuality(Common.SoftwareQuality.valueOf(entry.getKey().name()))
+ .setSeverity(Common.ImpactSeverity.valueOf(entry.getValue().name()))
+ .build())
+ .toList());
+
taintBuilder.setClosed(false);
taintBuilder.setMainLocation(locationBuilder.build());
issueDto.getOptionalRuleDescriptionContextKey().ifPresent(taintBuilder::setRuleDescriptionContextKey);
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"issue": {
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"severity": "MAJOR",
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,
-# The response contains a single protocol buffer message: TaintVulnerabilityPullQueryTimestamp followed by 0..n number of TaintLite protocol buffer messages.
+# The response contains a single protocol buffer message: TaintVulnerabilityPullQueryTimestamp followed by 0..n number of TaintVulnerabilityLite protocol buffer messages.
message TaintVulnerabilityPullQueryTimestamp {
required int64 queryTimestamp = 1;
}
-message TaintLite {
+message TaintVulnerabilityLite {
required string key = 1;
optional int64 creationDate = 2;
optional bool resolved = 3;
optional string ruleKey = 4;
- optional string severity = 5;
- optional string type = 6;
+ optional sonarqube.ws.commons.Severity severity = 5;
+ optional sonarqube.ws.commons.RuleType type = 6;
optional Location mainLocation = 7;
optional bool closed = 8;
- optional Flow flows = 9;
+ repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
+ optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 12;
+ optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 13;
+ repeated sonarqube.ws.commons.Impact impacts = 14;
}
message Location {
"status": "RESOLVED",
"resolution": "WONTFIX",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"message": "Remove this unused private \"getKee\" method.",
"messageFormattings": [
{
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,
"key": "AVibidgv1LF0E-ru2DVv",
"rule": "squid:S2301",
"severity": "MAJOR",
+ "cleanCodeAttribute": "CLEAR",
+ "cleanCodeAttributeCategory": "INTENTIONAL",
+ "impacts": [
+ {
+ "softwareQuality": "SECURITY",
+ "severity": "HIGH"
+ }
+ ],
"component": "org.sonarsource.sonarlint.intellij:sonarlint-intellij:src/main/java/org/sonarlint/intellij/core/ServerIssueUpdater.java",
"project": "org.sonarsource.sonarlint.intellij:sonarlint-intellij",
"line": 78,
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.sonar.api.resources.Languages;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.db.DbTester;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
+ assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getCleanCodeAttribute().name()));
+ assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getCleanCodeAttribute().getAttributeCategory().name()));
assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
assertThat(new ArrayList<>(issue.getCodeVariantsList())).containsExactlyInAnyOrderElementsOf(issueDto.getCodeVariants());
+ assertThat(issue.getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
+ .entrySet()
+ .stream()
+ .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
+ .collect(toList()));
}
@Test
componentDto = component;
issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component)
.setType(CODE_SMELL)
+ .setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
.setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
.setAssigneeUuid(userDto.getUuid())
.setResolution("resolution_" + randomAlphanumeric(5))
public static final String PARAM_TYPE = "type";
public static final String PARAM_ISSUES = "issues";
public static final String PARAM_SEVERITIES = "severities";
- public static final String PARAM_SOFTWARE_QUALITIES = "softwareQualities";
-
- //TODO: To be discussed for the naming
- public static final String PARAM_SOFTWARE_QUALITIES_SEVERTIIES = "softwareQualitiesSeverities";
+ public static final String PARAM_IMPACT_SOFTWARE_QUALITIES = "impactSoftwareQualities";
+ public static final String PARAM_IMPACT_SEVERITIES = "impactSeverities";
public static final String PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories";
public static final String PARAM_STATUSES = "statuses";
public static final String PARAM_RESOLUTIONS = "resolutions";
ALL = 2;
}
+enum CleanCodeAttribute {
+ CONVENTIONAL = 0;
+ FORMATTED = 1;
+ IDENTIFIABLE = 2;
+ CLEAR = 3;
+ COMPLETE = 4;
+ EFFICIENT = 5;
+ LOGICAL = 6;
+ DISTINCT = 7;
+ FOCUSED = 8;
+ MODULAR = 9;
+ TESTED = 10;
+ LAWFUL = 11;
+ RESPECTFUL = 12;
+ TRUSTWORTHY = 13;
+}
+
+enum CleanCodeAttributeCategory {
+ ADAPTABLE = 0;
+ CONSISTENT = 1;
+ INTENTIONAL = 2;
+ RESPONSIBLE = 3;
+}
+
+message Impact {
+ required SoftwareQuality softwareQuality = 1;
+ required ImpactSeverity severity = 2;
+}
+
+enum SoftwareQuality {
+ MAINTAINABILITY = 0;
+ RELIABILITY = 1;
+ SECURITY = 2;
+}
+
+enum ImpactSeverity {
+ LOW = 0;
+ MEDIUM = 1;
+ HIGH = 2;
+}
+
// Lines start at 1 and line offsets start at 0
message TextRange {
// Start line. Should never be absent
repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38;
repeated string codeVariants = 39;
+ optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 40;
+ optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 41;
+ repeated sonarqube.ws.commons.Impact impacts = 42;
}
message Transitions {
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
+ optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 12;
+ optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 13;
+ repeated sonarqube.ws.commons.Impact impacts = 14;
}
message Flow {