Browse Source

SONAR-20871 Add simple status to the issue index

tags/10.4.0.87286
Benjamin Campomenosi 6 months ago
parent
commit
0bc3c36c61
21 changed files with 302 additions and 53 deletions
  1. 11
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
  2. 18
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
  3. 2
    1
      server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
  4. 2
    0
      server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
  5. 11
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
  6. 11
    1
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
  7. 2
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
  8. 1
    0
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
  9. 9
    2
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  10. 12
    1
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
  11. 1
    0
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
  12. 28
    2
      server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
  13. 30
    3
      server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
  14. 18
    22
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java
  15. 91
    8
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
  16. 27
    8
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  17. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
  18. 1
    0
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json
  19. 2
    2
      sonar-core/src/main/java/org/sonar/core/issue/status/SimpleStatus.java
  20. 23
    0
      sonar-core/src/main/java/org/sonar/core/issue/status/package-info.java
  21. 1
    1
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java

+ 11
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java View File

@@ -23,9 +23,14 @@ import java.util.Collections;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.issue.status.SimpleStatus;

import static com.google.common.base.Preconditions.checkArgument;

public final class IndexedIssueDto {
private String issueKey = null;
@@ -117,6 +122,12 @@ public final class IndexedIssueDto {
return this;
}

@CheckForNull
public String getSimpleStatus() {
checkArgument(status != null, "Status must be initialized to retrieve simple status");
return Optional.ofNullable(SimpleStatus.of(status, resolution)).map(SimpleStatus::name).orElse(null);
}

public Long getEffort() {
return effort;
}
@@ -304,7 +315,6 @@ public final class IndexedIssueDto {
return ruleDefaultImpacts;
}


public Map<SoftwareQuality, Severity> getEffectiveImpacts() {
EnumMap<SoftwareQuality, Severity> effectiveImpacts = new EnumMap<>(SoftwareQuality.class);
ruleDefaultImpacts.forEach(impact -> effectiveImpacts.put(impact.getSoftwareQuality(), impact.getSeverity()));

+ 18
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java View File

@@ -19,11 +19,15 @@
*/
package org.sonar.db.issue;

import java.util.Set;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.core.issue.status.SimpleStatus;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.groups.Tuple.tuple;

public class IndexedIssueDtoTest {
@@ -87,4 +91,18 @@ public class IndexedIssueDtoTest {
.containsEntry(SoftwareQuality.SECURITY, Severity.HIGH);
}

@Test
public void getSimpleStatus_shouldReturnSimpleStatusFromStatusAndResolution() {
IndexedIssueDto issue1 = new IndexedIssueDto().setStatus(Issue.STATUS_OPEN);
IndexedIssueDto issue2 = new IndexedIssueDto().setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX);
IndexedIssueDto issue3 = new IndexedIssueDto().setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED);

assertThat(Set.of(issue1, issue2, issue3)).extracting(IndexedIssueDto::getSimpleStatus)
.containsExactlyInAnyOrder(SimpleStatus.OPEN.name(), SimpleStatus.ACCEPTED.name(), SimpleStatus.FIXED.name());
IndexedIssueDto issueWithStatusNull = new IndexedIssueDto();
assertThatThrownBy(issueWithStatusNull::getSimpleStatus)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Status must be initialized to retrieve simple status");
}

}

+ 2
- 1
server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java View File

@@ -71,8 +71,8 @@ import static org.sonar.server.es.Indexers.BranchEvent.DELETION;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_KEY_UPDATE;
import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_TAGS_UPDATE;
import static org.sonar.server.issue.IssueDocTesting.newDoc;
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
@@ -153,6 +153,7 @@ public class IssueIndexerIT {
.containsExactlyInAnyOrder(Map.of(
SUB_FIELD_SOFTWARE_QUALITY, SoftwareQuality.MAINTAINABILITY.name(),
SUB_FIELD_SEVERITY, Severity.HIGH.name()));
assertThat(doc.simpleStatus()).isEqualTo(issue.getSimpleStatus().name());
}

@Test

+ 2
- 0
server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java View File

@@ -28,6 +28,7 @@ import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ProjectData;
@@ -81,6 +82,7 @@ public class IssueIteratorFactoryIT {
assertThat(issue.key()).isEqualTo(expected.getKey());
assertThat(issue.resolution()).isEqualTo("FIXED");
assertThat(issue.status()).isEqualTo("RESOLVED");
assertThat(issue.simpleStatus()).isEqualTo(SimpleStatus.FIXED.name());
assertThat(issue.severity()).isEqualTo("BLOCKER");
assertThat(issue.assigneeUuid()).isEqualTo("uuid-of-guy1");
assertThat(issue.authorLogin()).isEqualTo("guy2");

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

@@ -61,6 +61,7 @@ public class SearchRequest {
private List<String> impactSoftwareQualities;
private List<String> cleanCodeAttributesCategories;
private List<String> statuses;
private List<String> simpleStatuses;
private List<String> tags;
private Set<String> types;
private List<String> pciDss32;
@@ -337,6 +338,16 @@ public class SearchRequest {
return this;
}

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

@CheckForNull
public List<String> getSimpleStatuses() {
return simpleStatuses;
}

@CheckForNull
public List<String> getTags() {
return tags;

+ 11
- 1
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java View File

@@ -35,8 +35,8 @@ import org.sonar.server.permission.index.AuthorizationDoc;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;

import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;

public class IssueDoc extends BaseDoc {
@@ -112,6 +112,11 @@ public class IssueDoc extends BaseDoc {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION);
}

@CheckForNull
public String simpleStatus() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_SIMPLE_STATUS);
}

@CheckForNull
public String assigneeUuid() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID);
@@ -236,6 +241,11 @@ public class IssueDoc extends BaseDoc {
return this;
}

public IssueDoc setSimpleStatus(String s) {
setField(IssueIndexDefinition.FIELD_ISSUE_SIMPLE_STATUS, s);
return this;
}

public IssueDoc setAssigneeUuid(@Nullable String s) {
setField(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, s);
return this;

+ 2
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java View File

@@ -86,6 +86,7 @@ public class IssueIndexDefinition implements IndexDefinition {
public static final String FIELD_ISSUE_SEVERITY = "severity";
public static final String FIELD_ISSUE_SEVERITY_VALUE = "severityValue";
public static final String FIELD_ISSUE_STATUS = "status";
public static final String FIELD_ISSUE_SIMPLE_STATUS = "simpleStatus";
public static final String FIELD_ISSUE_TAGS = "tags";
public static final String FIELD_ISSUE_TYPE = "type";
public static final String FIELD_ISSUE_PCI_DSS_32 = "pciDss-3.2";
@@ -160,6 +161,7 @@ public class IssueIndexDefinition implements IndexDefinition {
mapping.createBooleanField(FIELD_ISSUE_IS_MAIN_BRANCH);
mapping.keywordFieldBuilder(FIELD_ISSUE_DIRECTORY_PATH).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_RESOLUTION).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SIMPLE_STATUS).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_RULE_UUID).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SEVERITY).disableNorms().build();
mapping.createByteField(FIELD_ISSUE_SEVERITY_VALUE);

+ 1
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java View File

@@ -96,6 +96,7 @@ class IssueIteratorForSingleChunk implements IssueIterator {
.orElse(null);
doc.setCleanCodeAttributeCategory(cleanCodeAttributeCategory);
doc.setStatus(indexedIssueDto.getStatus());
doc.setSimpleStatus(indexedIssueDto.getSimpleStatus());
doc.setEffort(indexedIssueDto.getEffort());
doc.setAuthorLogin(indexedIssueDto.getAuthorLogin());


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

@@ -77,6 +77,7 @@ import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsUtils;
import org.sonar.server.es.SearchOptions;
@@ -144,6 +145,7 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
import static org.sonar.server.issue.index.IssueIndex.Facet.SIMPLE_STATUSES;
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;
@@ -181,6 +183,7 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SIMPLE_STATUS;
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;
@@ -214,6 +217,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SIMPLE_STATUSES;
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;
@@ -263,6 +267,7 @@ public class IssueIndex {
STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
// Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
SIMPLE_STATUSES(PARAM_SIMPLE_STATUSES, FIELD_ISSUE_SIMPLE_STATUS, STICKY, SimpleStatus.values().length),
TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
@@ -481,6 +486,7 @@ public class IssueIndex {
FIELD_ISSUE_RULE_UUID,
query.ruleUuids()));
filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
filters.addFilter(FIELD_ISSUE_SIMPLE_STATUS, SIMPLE_STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SIMPLE_STATUS, query.simpleStatuses()));
filters.addFilter(FIELD_ISSUE_CODE_VARIANTS, CODE_VARIANTS.getFilterScope(), createTermsFilter(FIELD_ISSUE_CODE_VARIANTS, query.codeVariants()));

// security category
@@ -839,6 +845,7 @@ public class IssueIndex {
private void configureTopAggregations(TopAggregationHelper aggregationHelper, IssueQuery query, SearchOptions options,
AllFilters queryFilters, SearchSourceBuilder esRequest) {
addFacetIfNeeded(options, aggregationHelper, esRequest, STATUSES, NO_SELECTED_VALUES);
addFacetIfNeeded(options, aggregationHelper, esRequest, SIMPLE_STATUSES, query.simpleStatuses().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, PROJECT_UUIDS, query.projectUuids().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, DIRECTORIES, query.directories().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, FILES, query.files().toArray());
@@ -924,7 +931,7 @@ public class IssueIndex {
.map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(),
query.impactSeverities().isEmpty() ? mainQuery.apply(softwareQuality)
: mainQuery.apply(softwareQuality)
.filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()))))
.filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()))))
.toArray(FiltersAggregator.KeyedFilter[]::new);

AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
@@ -948,7 +955,7 @@ public class IssueIndex {
.map(severity -> new FiltersAggregator.KeyedFilter(severity.name(),
query.impactSoftwareQualities().isEmpty() ? mainQuery.apply(severity)
: mainQuery.apply(severity)
.filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))))
.filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))))
.toArray(FiltersAggregator.KeyedFilter[]::new);

AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(

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

@@ -62,6 +62,7 @@ public class IssueQuery {
private final Collection<String> impactSeverities;
private final Collection<String> impactSoftwareQualities;
private final Collection<String> statuses;
private final Collection<String> simpleStatuses;
private final Collection<String> resolutions;
private final Collection<String> components;
private final Collection<String> projects;
@@ -110,6 +111,7 @@ public class IssueQuery {
this.impactSoftwareQualities = defaultCollection(builder.impactSoftwareQualities);
this.statuses = defaultCollection(builder.statuses);
this.resolutions = defaultCollection(builder.resolutions);
this.simpleStatuses = defaultCollection(builder.simpleStatuses);
this.components = defaultCollection(builder.components);
this.projects = defaultCollection(builder.projects);
this.directories = defaultCollection(builder.directories);
@@ -171,6 +173,10 @@ public class IssueQuery {
return statuses;
}

public Collection<String> simpleStatuses() {
return simpleStatuses;
}

public Collection<String> resolutions() {
return resolutions;
}
@@ -179,7 +185,6 @@ public class IssueQuery {
return components;
}


public Collection<String> projectUuids() {
return projects;
}
@@ -363,6 +368,7 @@ public class IssueQuery {
private Collection<String> impactSoftwareQualities;
private Collection<String> statuses;
private Collection<String> resolutions;
private Collection<String> simpleStatuses;
private Collection<String> components;
private Collection<String> projects;
private Collection<String> directories;
@@ -427,6 +433,11 @@ public class IssueQuery {
return this;
}

public Builder simpleStatuses(@Nullable Collection<String> l) {
this.simpleStatuses = l;
return this;
}

public Builder componentUuids(@Nullable Collection<String> l) {
this.components = l;
return this;

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

@@ -129,6 +129,7 @@ public class IssueQueryFactory {
.impactSeverities(request.getImpactSeverities())
.statuses(request.getStatuses())
.resolutions(request.getResolutions())
.simpleStatuses(request.getSimpleStatuses())
.resolved(request.getResolved())
.rules(ruleDtos)
.ruleUuids(ruleUuids)

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

@@ -29,6 +29,7 @@ import org.junit.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.server.es.Facets;
@@ -370,6 +371,31 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L));
}

@Test
public void search_shouldReturnSimpleStatusesFacet() {
ComponentDto mainBranch = newPrivateProjectDto();
ComponentDto file = newFileDto(mainBranch);

indexIssues(
newDoc("I1", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.CONFIRMED.name()),
newDoc("I2", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FIXED.name()),
newDoc("I3", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()),
newDoc("I4", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()),
newDoc("I5", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I6", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I7", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I8", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name()),
newDoc("I9", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name()));

assertThatFacetHasSize(IssueQuery.builder().build(), "simpleStatuses", 5);
assertThatFacetHasOnly(IssueQuery.builder(), "simpleStatuses",
entry("OPEN", 2L),
entry("CONFIRMED", 1L),
entry("FALSE_POSITIVE", 2L),
entry("ACCEPTED", 3L),
entry("FIXED", 1L));
}

@Test
public void facets_on_resolutions_return_5_entries_max() {
ComponentDto project = newPrivateProjectDto();
@@ -869,7 +895,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
}

@SafeVarargs
private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
private void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly(facet, "effort");
@@ -877,7 +903,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
}

@SafeVarargs
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
private void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly(facet, "effort");

+ 30
- 3
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java View File

@@ -28,6 +28,7 @@ import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.server.es.SearchOptions;
@@ -672,7 +673,7 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
assertThatThrownBy(() -> underTest.search(IssueQuery.builder()
.createdAfter(parseDate("2014-09-20")).createdBefore(parseDate("2014-09-20"))
.build(), new SearchOptions()))
.isInstanceOf(IllegalArgumentException.class);
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@@ -705,8 +706,8 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
public void fail_if_created_before_equals_created_after() {
assertThatThrownBy(() -> underTest.search(IssueQuery.builder().createdAfter(parseDate("2014-09-20"))
.createdBefore(parseDate("2014-09-20")).build(), new SearchOptions()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Start bound cannot be larger or equal to end bound");
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Start bound cannot be larger or equal to end bound");
}

@Test
@@ -923,6 +924,32 @@ public class IssueIndexFiltersTest extends IssueIndexTestCommon {
"I1", "I2", "I3", "I4", "I5", "I6", "I7", "I8");
}

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

indexIssues(
newDoc("I1", project.uuid(), file).setSimpleStatus(SimpleStatus.CONFIRMED.name()),
newDoc("I2", project.uuid(), file).setSimpleStatus(SimpleStatus.FIXED.name()),
newDoc("I3", project.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()),
newDoc("I4", project.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()),
newDoc("I5", project.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I6", project.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I7", project.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()),
newDoc("I8", project.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name()),
newDoc("I9", project.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name()));

assertThatSearchReturnsOnly(IssueQuery.builder().simpleStatuses(Set.of(SimpleStatus.CONFIRMED.name(), SimpleStatus.OPEN.name())),
"I1", "I3", "I4");

assertThatSearchReturnsOnly(IssueQuery.builder().simpleStatuses(Set.of(SimpleStatus.FALSE_POSITIVE.name(), SimpleStatus.ACCEPTED.name())),
"I5", "I6", "I7", "I8", "I9");

assertThatSearchReturnsOnly(IssueQuery.builder().simpleStatuses(Set.of(SimpleStatus.FIXED.name())),
"I2");
}

private void indexView(String viewUuid, List<String> projectBranchUuids) {
viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjectBranchUuids(projectBranchUuids));
}

+ 18
- 22
server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java View File

@@ -101,8 +101,8 @@ import static org.mockito.Mockito.verify;
import static org.sonar.api.issue.Issue.RESOLUTION_ACKNOWLEDGED;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_SAFE;
import static org.sonar.api.issue.Issue.STATUSES;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
@@ -591,29 +591,25 @@ public class SearchActionIT {
@Test
public void returns_only_hotspots_to_review_or_reviewed_of_project() {
ProjectData projectData = dbTester.components().insertPublicProject();
ComponentDto project = projectData.getMainBranchComponent();
ComponentDto mainBranch = projectData.getMainBranchComponent();

userSessionRule.registerProjects(projectData.getProjectDto());
indexPermissions();
ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
IssueDto[] hotspots = STATUSES.stream()
.map(status -> {
RuleDto rule = newRule(SECURITY_HOTSPOT);
return insertHotspot(rule, project, file, t -> t.setStatus(status));
})
.toArray(IssueDto[]::new);
ComponentDto file = dbTester.components().insertComponent(newFileDto(mainBranch));
RuleDto rule = newRule(SECURITY_HOTSPOT);
IssueDto toReviewHotspot = insertHotspot(rule, mainBranch, file, i -> i.setStatus(STATUS_TO_REVIEW));
IssueDto reviewedHotspot = insertHotspot(rule, mainBranch, file, i -> i.setStatus(STATUS_REVIEWED).setResolution(RESOLUTION_ACKNOWLEDGED));
IssueDto wrongStatusHotspot = insertHotspot(rule, mainBranch, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED));
indexIssues();
String[] expectedKeys = Arrays.stream(hotspots)
.filter(t -> STATUS_REVIEWED.equals(t.getStatus()) || STATUS_TO_REVIEW.equals(t.getStatus()))
.map(IssueDto::getKey)
.toArray(String[]::new);

SearchWsResponse response = newRequest(project)
SearchWsResponse response = newRequest(mainBranch)
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsOnly(expectedKeys);
.containsOnly(toReviewHotspot.getKey(), reviewedHotspot.getKey());
}

@Test
@@ -1789,7 +1785,7 @@ public class SearchActionIT {

SearchWsResponse responseOnLeak = newRequest(project,
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true"))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(Stream.concat(
@@ -1833,7 +1829,7 @@ public class SearchActionIT {

SearchWsResponse responseOnLeak = newRequest(project,
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true"))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(hotspotsInLeakPeriod
@@ -1871,7 +1867,7 @@ public class SearchActionIT {

SearchWsResponse responseOnLeak = newRequest(project,
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true"))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList()).isEmpty();
}

@@ -1904,7 +1900,7 @@ public class SearchActionIT {

SearchWsResponse responseOnLeak = newRequest(project,
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true").setParam(PARAM_PULL_REQUEST, "pr"))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList()).hasSize(3);
}

@@ -1947,7 +1943,7 @@ public class SearchActionIT {

SearchWsResponse responseOnLeak = newRequest(application.getMainBranchComponent(),
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true"))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(afterRef.getKey());
@@ -2000,14 +1996,14 @@ public class SearchActionIT {
ComponentDto applicationComponentDto = applicationData.getMainBranchComponent();
SearchWsResponse responseAll = newRequest(applicationComponentDto,
t -> t.setParam(PARAM_BRANCH, applicationBranch.getKey()))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseAll.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(afterRef.getKey(), atRef.getKey(), beforeRef.getKey(), project2Issue.getKey());

SearchWsResponse responseOnLeak = newRequest(applicationComponentDto,
t -> t.setParam(PARAM_IN_NEW_CODE_PERIOD, "true").setParam(PARAM_BRANCH, applicationBranch.getKey()))
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(afterRef.getKey());

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

@@ -48,6 +48,7 @@ import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.Durations;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.core.util.Uuids;
import org.sonar.db.DbClient;
@@ -99,9 +100,17 @@ import static org.apache.commons.lang.StringUtils.EMPTY;
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.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
import static org.sonar.api.issue.Issue.RESOLUTION_SAFE;
import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.server.ws.WebService.Param.FACETS;
@@ -136,6 +145,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFT
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;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SIMPLE_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;

public class SearchActionIT {
@@ -238,7 +248,7 @@ public class SearchActionIT {
.get(0)
.getActions()
.getActionsList())
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN));

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

@Test
@@ -594,6 +604,79 @@ public class SearchActionIT {
.assertJson(this.getClass(), "search_by_variants_with_facets.json");
}

@Test
public void search_whenFilteringBySimpleStatuses_shouldReturnSimpleStatusesFacet() {
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"));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_OPEN));
IssueDto expectedIssue = db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_WONT_FIX));
// security hotspot should be ignored
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REVIEWED).setResolution(RESOLUTION_SAFE));
indexPermissionsAndIssues();

SearchWsResponse response = ws.newRequest()
.setParam(PARAM_SIMPLE_STATUSES, SimpleStatus.ACCEPTED.name())
.setParam(FACETS, PARAM_SIMPLE_STATUSES)
.executeProtobuf(SearchWsResponse.class);

List<Issue> issuesList = response.getIssuesList();
assertThat(issuesList)
.extracting(Issue::getKey)
.containsExactlyInAnyOrder(expectedIssue.getKey());

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_SIMPLE_STATUSES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple(SimpleStatus.OPEN.name(), 1L),
tuple(SimpleStatus.ACCEPTED.name(), 1L),
tuple(SimpleStatus.FIXED.name(), 2L),
tuple(SimpleStatus.FALSE_POSITIVE.name(), 1L));
}

@Test
public void search_whenSimpleStatusesFacetRequested_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"));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_OPEN));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REOPENED));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CONFIRMED));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_REMOVED));
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_FIXED));
// security hotspot should be ignored
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REVIEWED).setResolution(RESOLUTION_SAFE));
indexPermissionsAndIssues();

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

Optional<Common.Facet> first = response.getFacets().getFacetsList()
.stream().filter(facet -> facet.getProperty().equals(PARAM_SIMPLE_STATUSES))
.findFirst();
assertThat(first.get().getValuesList())
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
.containsExactlyInAnyOrder(
tuple(SimpleStatus.OPEN.name(), 2L),
tuple(SimpleStatus.ACCEPTED.name(), 1L),
tuple(SimpleStatus.CONFIRMED.name(), 1L),
tuple(SimpleStatus.FIXED.name(), 3L),
tuple(SimpleStatus.FALSE_POSITIVE.name(), 1L));

}

@Test
public void search_whenImpactSoftwareQualitiesFacetRequested_shouldReturnFacet() {
RuleDto rule = newIssueRule();
@@ -635,7 +718,7 @@ public class SearchActionIT {
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().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH).setUuid(uuidFactory.create()))
.addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH).setUuid(uuidFactory.create())));
@@ -1128,7 +1211,7 @@ public class SearchActionIT {
assertThat(ws.newRequest()
.setMultiParam("author", singletonList("unknown"))
.executeProtobuf(SearchWsResponse.class).getIssuesList())
.isEmpty();
.isEmpty();
}

@Test
@@ -1999,7 +2082,7 @@ public class SearchActionIT {
"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",
"cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities");
"cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities", "simpleStatuses");

WebService.Param branch = def.param(PARAM_BRANCH);
assertThat(branch.isInternal()).isFalse();
@@ -2074,9 +2157,9 @@ public class SearchActionIT {
private RuleDto newIssueRule(String ruleKey, Consumer<RuleDto> consumer) {
RuleDto rule = newRule(RuleKey.of("xoo", ruleKey),
createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
.setLanguage("xoo")
.setName("Rule name")
.setStatus(RuleStatus.READY);
.setLanguage("xoo")
.setName("Rule name")
.setStatus(RuleStatus.READY);
consumer.accept(rule);
db.rules().insert(rule);
return rule;

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

@@ -44,6 +44,7 @@ import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.Paging;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.status.SimpleStatus;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto;
@@ -108,6 +109,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_IN_
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_IN_NEW_CODE_PERIOD;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
@@ -126,8 +129,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SIMPLE_STATUSES;
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;
@@ -166,8 +168,8 @@ public class SearchAction implements IssuesWsAction {
PARAM_CODE_VARIANTS,
PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES,
PARAM_IMPACT_SOFTWARE_QUALITIES,
PARAM_IMPACT_SEVERITIES
);
PARAM_IMPACT_SEVERITIES,
PARAM_SIMPLE_STATUSES);

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";
@@ -205,6 +207,14 @@ public class SearchAction implements IssuesWsAction {
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
new Change("10.4", format(NEW_PARAM_ADDED_MESSAGE, PARAM_SIMPLE_STATUSES)),
new Change("10.4", format("Parameters '%s' and '%s' are deprecated in favor of '%s'.", PARAM_RESOLUTIONS, PARAM_STATUSES, PARAM_SIMPLE_STATUSES)),
new Change("10.4", format("Parameter '%s' is deprecated.", PARAM_SEVERITIES)),
new Change("10.4", format(NEW_FACET_ADDED_MESSAGE, PARAM_SIMPLE_STATUSES)),
new Change("10.4", format("Facets '%s' and '%s' are deprecated in favor of '%s'", PARAM_RESOLUTIONS, PARAM_STATUSES, PARAM_SIMPLE_STATUSES)),
new Change("10.4", "Response field 'simpleStatus' added"),
new Change("10.4", "Response fields 'status' and 'resolutions' are deprecated, in favor of 'simpleStatus'"),
new Change("10.4", format("Possible value '%s' for 'simpleStatus' field is deprecated.", SimpleStatus.CONFIRMED)),
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)),
@@ -273,7 +283,8 @@ public class SearchAction implements IssuesWsAction {
action.createParam(PARAM_SEVERITIES)
.setDescription("Comma-separated list of severities")
.setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
.setPossibleValues(Severity.ALL);
.setPossibleValues(Severity.ALL)
.setDeprecatedSince("10.4");
action.createParam(PARAM_IMPACT_SOFTWARE_QUALITIES)
.setSince("10.2")
.setDescription("Comma-separated list of Software Qualities")
@@ -292,11 +303,13 @@ public class SearchAction implements IssuesWsAction {
action.createParam(PARAM_STATUSES)
.setDescription("Comma-separated list of statuses")
.setExampleValue(STATUS_OPEN + "," + STATUS_REOPENED)
.setPossibleValues(ISSUE_STATUSES);
.setPossibleValues(ISSUE_STATUSES)
.setDeprecatedSince("10.4");
action.createParam(PARAM_RESOLUTIONS)
.setDescription("Comma-separated list of resolutions")
.setExampleValue(RESOLUTION_FIXED + "," + RESOLUTION_REMOVED)
.setPossibleValues(RESOLUTIONS);
.setPossibleValues(RESOLUTIONS)
.setDeprecatedSince("10.4");
action.createParam(PARAM_RESOLVED)
.setDescription("To match resolved or unresolved issues")
.setBooleanPossibleValues();
@@ -396,6 +409,11 @@ public class SearchAction implements IssuesWsAction {
.setDescription("Comma-separated list of code variants.")
.setExampleValue("windows,linux")
.setSince("10.1");
action.createParam(PARAM_SIMPLE_STATUSES)
.setDescription("")
.setPossibleValues(SimpleStatus.values())
.setExampleValue("%s,%S".formatted(SimpleStatus.ACCEPTED, SimpleStatus.FIXED))
.setSince("10.4");
}

private static void addComponentRelatedParams(WebService.NewAction action) {
@@ -463,7 +481,7 @@ public class SearchAction implements IssuesWsAction {
.filter(FACETS_REQUIRING_PROJECT::contains)
.collect(Collectors.toSet());
checkArgument(facetsRequiringProjectParameter.isEmpty() ||
(!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
(!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
String.join(",", facetsRequiringProjectParameter));

// execute request
@@ -620,6 +638,7 @@ public class SearchAction implements IssuesWsAction {
.setImpactSoftwareQualities(request.paramAsStrings(PARAM_IMPACT_SOFTWARE_QUALITIES))
.setCleanCodeAttributesCategories(request.paramAsStrings(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES))
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setSimpleStatuses(request.paramAsStrings(PARAM_SIMPLE_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))
.setPciDss32(request.paramAsStrings(PARAM_PCI_DSS_32))

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

@@ -205,7 +205,7 @@ public class SearchResponseFormat {
ofNullable(data.getUserByUuid(dto.getAssigneeUuid())).ifPresent(assignee -> issueBuilder.setAssignee(assignee.getLogin()));
ofNullable(emptyToNull(dto.getResolution())).ifPresent(issueBuilder::setResolution);
issueBuilder.setStatus(dto.getStatus());
issueBuilder.setSimpleStatus(SimpleStatus.of(dto.getStatus(), dto.getResolution()).name());
Optional.ofNullable(SimpleStatus.of(dto.getStatus(), dto.getResolution())).map(SimpleStatus::name).ifPresent(issueBuilder::setSimpleStatus);
issueBuilder.setMessage(nullToEmpty(dto.getMessage()));
issueBuilder.addAllMessageFormattings(MessageFormattingUtils.dbMessageFormattingToWs(dto.parseMessageFormattings()));
issueBuilder.addAllTags(dto.getTags());

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

@@ -15,6 +15,7 @@
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
"simpleStatus": "ACCEPTED",
"impacts": [
{
"softwareQuality": "SECURITY",

+ 2
- 2
sonar-core/src/main/java/org/sonar/core/issue/status/SimpleStatus.java View File

@@ -40,13 +40,13 @@ public enum SimpleStatus {
return SimpleStatus.CONFIRMED;
case Issue.STATUS_CLOSED:
return SimpleStatus.FIXED;
//Security hotspot should not return simple status as they are deprecated.
// Security hotspot should not return simple status as they are deprecated.
case Issue.STATUS_REVIEWED:
case Issue.STATUS_TO_REVIEW:
return null;
default:
}
if (resolution != null) {
if (Issue.STATUS_RESOLVED.equals(status) && resolution != null) {
switch (resolution) {
case Issue.RESOLUTION_FALSE_POSITIVE:
return SimpleStatus.FALSE_POSITIVE;

+ 23
- 0
sonar-core/src/main/java/org/sonar/core/issue/status/package-info.java View File

@@ -0,0 +1,23 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
@ParametersAreNonnullByDefault
package org.sonar.core.issue.status;

import javax.annotation.ParametersAreNonnullByDefault;

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

@@ -59,6 +59,7 @@ public class IssuesWsParameters {
public static final String PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories";
public static final String PARAM_STATUSES = "statuses";
public static final String PARAM_RESOLUTIONS = "resolutions";
public static final String PARAM_SIMPLE_STATUSES = "simpleStatuses";
public static final String PARAM_RESOLVED = "resolved";
public static final String PARAM_COMPONENTS = "components";
public static final String PARAM_COMPONENT_KEYS = "componentKeys";
@@ -79,7 +80,6 @@ public class IssuesWsParameters {
public static final String PARAM_SEND_NOTIFICATIONS = "sendNotifications";
public static final String PARAM_ASSIGNEES = "assignees";


public static final String PARAM_AUTHOR = "author";
public static final String PARAM_SCOPES = "scopes";
public static final String PARAM_LANGUAGES = "languages";

Loading…
Cancel
Save