aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorBenjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com>2023-11-02 14:52:28 +0100
committersonartech <sonartech@sonarsource.com>2023-11-08 20:02:52 +0000
commit0bc3c36c61a82eb18a66e2433164d06d548d127a (patch)
tree30efe10e56cf79ffbf8e56f6672ebe4669ab3ad4 /server
parenta1be2cd1286ff3a24fc27d9c9a387069f5eafb91 (diff)
downloadsonarqube-0bc3c36c61a82eb18a66e2433164d06d548d127a.tar.gz
sonarqube-0bc3c36c61a82eb18a66e2433164d06d548d127a.zip
SONAR-20871 Add simple status to the issue index
Diffstat (limited to 'server')
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java12
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java18
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java3
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java11
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java12
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java1
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java11
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java13
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java1
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java30
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java33
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java40
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java99
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java35
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json1
18 files changed, 276 insertions, 50 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
index ec56e1ef8d9..8e4b93ec44f 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IndexedIssueDto.java
@@ -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()));
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
index d4ef1a54658..7c05b77b54d 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IndexedIssueDtoTest.java
@@ -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");
+ }
+
}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
index 5e2db299839..4bc12e92e33 100644
--- a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
@@ -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
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
index 15291533dc1..10acc9a28a6 100644
--- a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
@@ -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");
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
index 3eb488a5951..a47d23d637b 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
@@ -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;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
index 48d9c6e0f4d..c9945d0b805 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
@@ -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 {
@@ -113,6 +113,11 @@ public class IssueDoc extends BaseDoc {
}
@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;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
index 5c760d638bf..e6d0478661b 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
@@ -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);
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
index 97222e5315f..0fdd1884526 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
@@ -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());
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
index 08cf93e12a4..e9da3ec2e45 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
@@ -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(
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
index 33eefc1e188..291ba957330 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
@@ -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;
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
index 27562572a1a..6412996c006 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
@@ -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)
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
index 2f15ebbde63..a2bd2f309ca 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java
@@ -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;
@@ -371,6 +372,31 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon {
}
@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();
ComponentDto file = newFileDto(project);
@@ -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");
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
index d6f0c5a7519..8292c9caac8 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
@@ -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));
}
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java
index fa19542e103..d65b2ed0409 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionIT.java
@@ -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());
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
index c29575c6e44..f40b1057b8d 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
@@ -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
@@ -595,6 +605,79 @@ public class SearchActionIT {
}
@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();
ComponentDto project = db.components().insertPublicProject("PROJECT_ID",
@@ -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;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
index f16f97a6d96..e65fbf1098e 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
@@ -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))
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
index 0761d1a56fe..a3b573bab23 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
@@ -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());
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json
index 0a664cad9dd..7be9a9bad45 100644
--- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json
+++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/issue/ws/search-example.json
@@ -15,6 +15,7 @@
"severity": "MAJOR",
"cleanCodeAttribute": "CLEAR",
"cleanCodeAttributeCategory": "INTENTIONAL",
+ "simpleStatus": "ACCEPTED",
"impacts": [
{
"softwareQuality": "SECURITY",