@@ -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())); |
@@ -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"); | |||
} | |||
} |
@@ -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 |
@@ -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"); |
@@ -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; |
@@ -35,8 +35,8 @@ import org.sonar.server.permission.index.AuthorizationDoc; | |||
import org.sonar.server.security.SecurityStandards; | |||
import org.sonar.server.security.SecurityStandards.VulnerabilityProbability; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE; | |||
public class IssueDoc extends BaseDoc { | |||
@@ -112,6 +112,11 @@ public class IssueDoc extends BaseDoc { | |||
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION); | |||
} | |||
@CheckForNull | |||
public String simpleStatus() { | |||
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_SIMPLE_STATUS); | |||
} | |||
@CheckForNull | |||
public String assigneeUuid() { | |||
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID); | |||
@@ -236,6 +241,11 @@ public class IssueDoc extends BaseDoc { | |||
return this; | |||
} | |||
public IssueDoc setSimpleStatus(String s) { | |||
setField(IssueIndexDefinition.FIELD_ISSUE_SIMPLE_STATUS, s); | |||
return this; | |||
} | |||
public IssueDoc setAssigneeUuid(@Nullable String s) { | |||
setField(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, s); | |||
return this; |
@@ -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); |
@@ -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()); | |||
@@ -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( |
@@ -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; |
@@ -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) |
@@ -29,6 +29,7 @@ import org.junit.Test; | |||
import org.sonar.api.issue.impact.Severity; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion; | |||
import org.sonar.core.issue.status.SimpleStatus; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.rule.RuleDto; | |||
import org.sonar.server.es.Facets; | |||
@@ -370,6 +371,31 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { | |||
assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L)); | |||
} | |||
@Test | |||
public void search_shouldReturnSimpleStatusesFacet() { | |||
ComponentDto mainBranch = newPrivateProjectDto(); | |||
ComponentDto file = newFileDto(mainBranch); | |||
indexIssues( | |||
newDoc("I1", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.CONFIRMED.name()), | |||
newDoc("I2", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FIXED.name()), | |||
newDoc("I3", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()), | |||
newDoc("I4", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.OPEN.name()), | |||
newDoc("I5", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()), | |||
newDoc("I6", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()), | |||
newDoc("I7", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.ACCEPTED.name()), | |||
newDoc("I8", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name()), | |||
newDoc("I9", mainBranch.uuid(), file).setSimpleStatus(SimpleStatus.FALSE_POSITIVE.name())); | |||
assertThatFacetHasSize(IssueQuery.builder().build(), "simpleStatuses", 5); | |||
assertThatFacetHasOnly(IssueQuery.builder(), "simpleStatuses", | |||
entry("OPEN", 2L), | |||
entry("CONFIRMED", 1L), | |||
entry("FALSE_POSITIVE", 2L), | |||
entry("ACCEPTED", 3L), | |||
entry("FIXED", 1L)); | |||
} | |||
@Test | |||
public void facets_on_resolutions_return_5_entries_max() { | |||
ComponentDto project = newPrivateProjectDto(); | |||
@@ -869,7 +895,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { | |||
} | |||
@SafeVarargs | |||
private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
private void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet))); | |||
Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId()); | |||
assertThat(facets.getNames()).containsOnly(facet, "effort"); | |||
@@ -877,7 +903,7 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { | |||
} | |||
@SafeVarargs | |||
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
private void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet))); | |||
Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId()); | |||
assertThat(facets.getNames()).containsOnly(facet, "effort"); |
@@ -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)); | |||
} |
@@ -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()); |
@@ -48,6 +48,7 @@ import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.status.SimpleStatus; | |||
import org.sonar.core.util.UuidFactoryFast; | |||
import org.sonar.core.util.Uuids; | |||
import org.sonar.db.DbClient; | |||
@@ -99,9 +100,17 @@ import static org.apache.commons.lang.StringUtils.EMPTY; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_SAFE; | |||
import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; | |||
import static org.sonar.api.issue.Issue.STATUS_CLOSED; | |||
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; | |||
import static org.sonar.api.issue.Issue.STATUS_OPEN; | |||
import static org.sonar.api.issue.Issue.STATUS_REOPENED; | |||
import static org.sonar.api.issue.Issue.STATUS_RESOLVED; | |||
import static org.sonar.api.issue.Issue.STATUS_REVIEWED; | |||
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; | |||
import static org.sonar.api.rules.RuleType.CODE_SMELL; | |||
import static org.sonar.api.server.ws.WebService.Param.FACETS; | |||
@@ -136,6 +145,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFT | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IN_NEW_CODE_PERIOD; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SIMPLE_STATUSES; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES; | |||
public class SearchActionIT { | |||
@@ -238,7 +248,7 @@ public class SearchActionIT { | |||
.get(0) | |||
.getActions() | |||
.getActionsList()) | |||
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN)); | |||
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY, ACTION_ASSIGN)); | |||
response = ws.newRequest() | |||
.setParam(PARAM_ADDITIONAL_FIELDS, "actions") | |||
@@ -251,7 +261,7 @@ public class SearchActionIT { | |||
.get(0) | |||
.getActions() | |||
.getActionsList()) | |||
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY)); | |||
.isEqualTo(asList(ACTION_SET_TAGS, COMMENT_KEY)); | |||
} | |||
@Test | |||
@@ -594,6 +604,79 @@ public class SearchActionIT { | |||
.assertJson(this.getClass(), "search_by_variants_with_facets.json"); | |||
} | |||
@Test | |||
public void search_whenFilteringBySimpleStatuses_shouldReturnSimpleStatusesFacet() { | |||
RuleDto rule = newIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject("PROJECT_ID", | |||
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java")); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_OPEN)); | |||
IssueDto expectedIssue = db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_WONT_FIX)); | |||
// security hotspot should be ignored | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REVIEWED).setResolution(RESOLUTION_SAFE)); | |||
indexPermissionsAndIssues(); | |||
SearchWsResponse response = ws.newRequest() | |||
.setParam(PARAM_SIMPLE_STATUSES, SimpleStatus.ACCEPTED.name()) | |||
.setParam(FACETS, PARAM_SIMPLE_STATUSES) | |||
.executeProtobuf(SearchWsResponse.class); | |||
List<Issue> issuesList = response.getIssuesList(); | |||
assertThat(issuesList) | |||
.extracting(Issue::getKey) | |||
.containsExactlyInAnyOrder(expectedIssue.getKey()); | |||
Optional<Common.Facet> first = response.getFacets().getFacetsList() | |||
.stream().filter(facet -> facet.getProperty().equals(PARAM_SIMPLE_STATUSES)) | |||
.findFirst(); | |||
assertThat(first.get().getValuesList()) | |||
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) | |||
.containsExactlyInAnyOrder( | |||
tuple(SimpleStatus.OPEN.name(), 1L), | |||
tuple(SimpleStatus.ACCEPTED.name(), 1L), | |||
tuple(SimpleStatus.FIXED.name(), 2L), | |||
tuple(SimpleStatus.FALSE_POSITIVE.name(), 1L)); | |||
} | |||
@Test | |||
public void search_whenSimpleStatusesFacetRequested_shouldReturnFacet() { | |||
RuleDto rule = newIssueRule(); | |||
ComponentDto project = db.components().insertPublicProject("PROJECT_ID", | |||
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java")); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_OPEN)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REOPENED)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CONFIRMED)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FIXED)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_REMOVED)); | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_FIXED)); | |||
// security hotspot should be ignored | |||
db.issues().insertIssue(rule, project, file, i -> i.setStatus(STATUS_REVIEWED).setResolution(RESOLUTION_SAFE)); | |||
indexPermissionsAndIssues(); | |||
SearchWsResponse response = ws.newRequest() | |||
.setParam(FACETS, PARAM_SIMPLE_STATUSES) | |||
.executeProtobuf(SearchWsResponse.class); | |||
Optional<Common.Facet> first = response.getFacets().getFacetsList() | |||
.stream().filter(facet -> facet.getProperty().equals(PARAM_SIMPLE_STATUSES)) | |||
.findFirst(); | |||
assertThat(first.get().getValuesList()) | |||
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) | |||
.containsExactlyInAnyOrder( | |||
tuple(SimpleStatus.OPEN.name(), 2L), | |||
tuple(SimpleStatus.ACCEPTED.name(), 1L), | |||
tuple(SimpleStatus.CONFIRMED.name(), 1L), | |||
tuple(SimpleStatus.FIXED.name(), 3L), | |||
tuple(SimpleStatus.FALSE_POSITIVE.name(), 1L)); | |||
} | |||
@Test | |||
public void search_whenImpactSoftwareQualitiesFacetRequested_shouldReturnFacet() { | |||
RuleDto rule = newIssueRule(); | |||
@@ -635,7 +718,7 @@ public class SearchActionIT { | |||
ComponentDto project = db.components().insertPublicProject("PROJECT_ID", | |||
c -> c.setKey("PROJECT_KEY").setName("NAME_PROJECT_ID").setLongName("LONG_NAME_PROJECT_ID").setLanguage("java")).getMainBranchComponent(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java")); | |||
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i | |||
.addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH).setUuid(uuidFactory.create())) | |||
.addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH).setUuid(uuidFactory.create()))); | |||
@@ -1128,7 +1211,7 @@ public class SearchActionIT { | |||
assertThat(ws.newRequest() | |||
.setMultiParam("author", singletonList("unknown")) | |||
.executeProtobuf(SearchWsResponse.class).getIssuesList()) | |||
.isEmpty(); | |||
.isEmpty(); | |||
} | |||
@Test | |||
@@ -1999,7 +2082,7 @@ public class SearchActionIT { | |||
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", | |||
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0", | |||
"owaspAsvsLevel", "owaspTop10", "owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod", "codeVariants", | |||
"cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities"); | |||
"cleanCodeAttributeCategories", "impactSeverities", "impactSoftwareQualities", "simpleStatuses"); | |||
WebService.Param branch = def.param(PARAM_BRANCH); | |||
assertThat(branch.isInternal()).isFalse(); | |||
@@ -2074,9 +2157,9 @@ public class SearchActionIT { | |||
private RuleDto newIssueRule(String ruleKey, Consumer<RuleDto> consumer) { | |||
RuleDto rule = newRule(RuleKey.of("xoo", ruleKey), | |||
createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc")) | |||
.setLanguage("xoo") | |||
.setName("Rule name") | |||
.setStatus(RuleStatus.READY); | |||
.setLanguage("xoo") | |||
.setName("Rule name") | |||
.setStatus(RuleStatus.READY); | |||
consumer.accept(rule); | |||
db.rules().insert(rule); | |||
return rule; |
@@ -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)) |
@@ -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()); |
@@ -15,6 +15,7 @@ | |||
"severity": "MAJOR", | |||
"cleanCodeAttribute": "CLEAR", | |||
"cleanCodeAttributeCategory": "INTENTIONAL", | |||
"simpleStatus": "ACCEPTED", | |||
"impacts": [ | |||
{ | |||
"softwareQuality": "SECURITY", |
@@ -40,13 +40,13 @@ public enum SimpleStatus { | |||
return SimpleStatus.CONFIRMED; | |||
case Issue.STATUS_CLOSED: | |||
return SimpleStatus.FIXED; | |||
//Security hotspot should not return simple status as they are deprecated. | |||
// Security hotspot should not return simple status as they are deprecated. | |||
case Issue.STATUS_REVIEWED: | |||
case Issue.STATUS_TO_REVIEW: | |||
return null; | |||
default: | |||
} | |||
if (resolution != null) { | |||
if (Issue.STATUS_RESOLVED.equals(status) && resolution != null) { | |||
switch (resolution) { | |||
case Issue.RESOLUTION_FALSE_POSITIVE: | |||
return SimpleStatus.FALSE_POSITIVE; |
@@ -0,0 +1,23 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.core.issue.status; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -59,6 +59,7 @@ public class IssuesWsParameters { | |||
public static final String PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES = "cleanCodeAttributeCategories"; | |||
public static final String PARAM_STATUSES = "statuses"; | |||
public static final String PARAM_RESOLUTIONS = "resolutions"; | |||
public static final String PARAM_SIMPLE_STATUSES = "simpleStatuses"; | |||
public static final String PARAM_RESOLVED = "resolved"; | |||
public static final String PARAM_COMPONENTS = "components"; | |||
public static final String PARAM_COMPONENT_KEYS = "componentKeys"; | |||
@@ -79,7 +80,6 @@ public class IssuesWsParameters { | |||
public static final String PARAM_SEND_NOTIFICATIONS = "sendNotifications"; | |||
public static final String PARAM_ASSIGNEES = "assignees"; | |||
public static final String PARAM_AUTHOR = "author"; | |||
public static final String PARAM_SCOPES = "scopes"; | |||
public static final String PARAM_LANGUAGES = "languages"; |