diff options
author | Benjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com> | 2023-11-02 14:52:28 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-11-08 20:02:52 +0000 |
commit | 0bc3c36c61a82eb18a66e2433164d06d548d127a (patch) | |
tree | 30efe10e56cf79ffbf8e56f6672ebe4669ab3ad4 /server | |
parent | a1be2cd1286ff3a24fc27d9c9a387069f5eafb91 (diff) | |
download | sonarqube-0bc3c36c61a82eb18a66e2433164d06d548d127a.tar.gz sonarqube-0bc3c36c61a82eb18a66e2433164d06d548d127a.zip |
SONAR-20871 Add simple status to the issue index
Diffstat (limited to 'server')
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", |