Bladeren bron

SONAR-15904 - Adapt Issue Search to work with new code reference

tags/9.3.0.51899
Belen Pruvost 2 jaren geleden
bovenliggende
commit
9d1361c434

+ 9
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java Bestand weergeven

@@ -336,4 +336,13 @@ public class IssueDoc extends BaseDoc {
setField(IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY, v == null ? null : v.getScore());
return this;
}

public boolean isNewCodeReference() {
return getField(IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE);
}

public IssueDoc setIsNewCodeReference(boolean b) {
setField(IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE, b);
return this;
}
}

+ 6
- 0
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java Bestand weergeven

@@ -102,6 +102,11 @@ public class IssueIndexDefinition implements IndexDefinition {
public static final String FIELD_ISSUE_SQ_SECURITY_CATEGORY = "sonarsourceSecurity";
public static final String FIELD_ISSUE_VULNERABILITY_PROBABILITY = "vulnerabilityProbability";

/**
* Whether issue is new code for a branch using the reference branch new code definition.
*/
public static final String FIELD_ISSUE_NEW_CODE_REFERENCE = "isNewCodeReference";

private final Configuration config;
private final boolean enableSource;

@@ -163,5 +168,6 @@ public class IssueIndexDefinition implements IndexDefinition {
mapping.keywordFieldBuilder(FIELD_ISSUE_CWE).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SQ_SECURITY_CATEGORY).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_VULNERABILITY_PROBABILITY).disableNorms().build();
mapping.createBooleanField(FIELD_ISSUE_NEW_CODE_REFERENCE);
}
}

+ 8
- 2
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java Bestand weergeven

@@ -42,6 +42,7 @@ import org.sonar.db.ResultSetIterator;
import org.sonar.server.security.SecurityStandards;

import static com.google.common.base.Preconditions.checkArgument;
import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.db.DatabaseUtils.getLong;
import static org.sonar.db.rule.RuleDefinitionDto.deserializeSecurityStandardsString;
@@ -81,16 +82,19 @@ class IssueIteratorForSingleChunk implements IssueIterator {
"i.tags",
"i.issue_type",
"r.security_standards",
"c.qualifier"
"c.qualifier",
"n.uuid"
};

private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
"inner join rules r on r.uuid = i.rule_uuid " +
"inner join components c on c.uuid = i.component_uuid ";

private static final String SQL_NEW_CODE_JOIN = "left join new_code_reference_issues n on n.issue_key = i.kee ";

private static final String PROJECT_FILTER = " and c.project_uuid = ? and i.project_uuid = ? ";
private static final String ISSUE_KEY_FILTER_PREFIX = " and i.kee in (";
private static final String ISSUE_KEY_FILTER_SUFFIX = ")";
private static final String ISSUE_KEY_FILTER_SUFFIX = ") ";

static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
static final Splitter MODULE_PATH_SPLITTER = Splitter.on('.').trimResults().omitEmptyStrings();
@@ -151,6 +155,7 @@ class IssueIteratorForSingleChunk implements IssueIterator {
sql += IntStream.range(0, issueKeys.size()).mapToObj(i -> "?").collect(Collectors.joining(","));
sql += ISSUE_KEY_FILTER_SUFFIX;
}
sql += SQL_NEW_CODE_JOIN;
return sql;
}

@@ -237,6 +242,7 @@ class IssueIteratorForSingleChunk implements IssueIterator {
doc.setVulnerabilityProbability(sqCategory.getVulnerability());

doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(23)) ? IssueScope.TEST : IssueScope.MAIN);
doc.setIsNewCodeReference(!isNullOrEmpty(rs.getString(24)));
return doc;
}


+ 5
- 0
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIteratorFactoryTest.java Bestand weergeven

@@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;

public class IssueIteratorFactoryTest {

@@ -119,11 +120,15 @@ public class IssueIteratorFactoryTest {
IssueDto dirIssue = dbTester.issues().insert(rule, project, directory);
IssueDto projectIssue = dbTester.issues().insert(rule, project, project);

dbTester.issues().insertNewCodeReferenceIssue(newCodeReferenceIssue(fileIssue));

Map<String, IssueDoc> issuesByKey = issuesByKey();

assertThat(issuesByKey)
.hasSize(4)
.containsOnlyKeys(fileIssue.getKey(), moduleIssue.getKey(), dirIssue.getKey(), projectIssue.getKey());

assertThat(issuesByKey.get(fileIssue.getKey()).isNewCodeReference()).isTrue();
}

@Test

+ 31
- 6
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java Bestand weergeven

@@ -149,6 +149,7 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANG
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LINE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
@@ -452,6 +453,8 @@ public class IssueIndex {
addComponentRelatedFilters(query, filters);
addDatesFilter(filters, query);
addCreatedAfterByProjectsFilter(filters, query);
addNewCodeReferenceFilter(filters, query);
addNewCodeReferenceFilterByProjectsFilter(filters, query);
return filters;
}

@@ -562,11 +565,11 @@ public class IssueIndex {
private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
Collection<String> facetNames = options.getFacets();
Set<TopAggregationDefinition<?>> facets = Stream.concat(
Stream.of(EFFORT_TOP_AGGREGATION),
facetNames.stream()
.map(FACETS_BY_NAME::get)
.filter(Objects::nonNull)
.map(Facet::getTopAggregationDef))
Stream.of(EFFORT_TOP_AGGREGATION),
facetNames.stream()
.map(FACETS_BY_NAME::get)
.filter(Objects::nonNull)
.map(Facet::getTopAggregationDef))
.collect(MoreCollectors.toSet(facetNames.size()));

return new RequestFiltersComputer(allFilters, facets);
@@ -645,6 +648,28 @@ public class IssueIndex {
}
}

private static void addNewCodeReferenceFilter(AllFilters filters, IssueQuery query) {
Boolean newCodeOnReference = query.newCodeOnReference();

if (newCodeOnReference != null) {
filters.addFilter(
FIELD_ISSUE_NEW_CODE_REFERENCE, new SimpleFieldFilterScope(FIELD_ISSUE_NEW_CODE_REFERENCE),
termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true));
}
}

private static void addNewCodeReferenceFilterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
Collection<String> newCodeOnReferenceByProjectUuids = query.newCodeOnReferenceByProjectUuids();
BoolQueryBuilder boolQueryBuilder = boolQuery();

newCodeOnReferenceByProjectUuids.forEach(projectOrProjectBranchUuid -> boolQueryBuilder.should(boolQuery()
.filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
.filter(termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true))));

allFilters.addFilter("__is_new_code_reference_by_project_uuids", new SimpleFieldFilterScope("newCodeReferenceByProjectUuids"), boolQueryBuilder);
}


private static void addCreatedAfterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
BoolQueryBuilder boolQueryBuilder = boolQuery();
@@ -865,7 +890,7 @@ public class IssueIndex {
t ->
// add sub-aggregation to return issue count for current user
aggregationHelper.getSubAggregationHelper()
.buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid})
.buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[]{uuid})
.ifPresent(t::subAggregation));
esRequest.aggregation(aggregation);
}

+ 25
- 0
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java Bestand weergeven

@@ -97,6 +97,8 @@ public class IssueQuery {
private final String branchUuid;
private final Boolean mainBranch;
private final ZoneId timeZone;
private final Boolean newCodeOnReference;
private final Collection<String> newCodeOnReferenceByProjectUuids;

private IssueQuery(Builder builder) {
this.issueKeys = defaultCollection(builder.issueKeys);
@@ -135,6 +137,8 @@ public class IssueQuery {
this.branchUuid = builder.branchUuid;
this.mainBranch = builder.mainBranch;
this.timeZone = builder.timeZone;
this.newCodeOnReference = builder.newCodeOnReference;
this.newCodeOnReferenceByProjectUuids = defaultCollection(builder.newCodeOnReferenceByProjectUuids);
}

public Collection<String> issueKeys() {
@@ -300,6 +304,15 @@ public class IssueQuery {
return timeZone;
}

@CheckForNull
public Boolean newCodeOnReference() {
return newCodeOnReference;
}

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

public static class Builder {
private Collection<String> issueKeys;
private Collection<String> severities;
@@ -337,6 +350,8 @@ public class IssueQuery {
private String branchUuid;
private Boolean mainBranch = true;
private ZoneId timeZone;
private Boolean newCodeOnReference = null;
private Collection<String> newCodeOnReferenceByProjectUuids;

private Builder() {

@@ -548,6 +563,16 @@ public class IssueQuery {
this.timeZone = timeZone;
return this;
}

public Builder newCodeOnReference(@Nullable Boolean newCodeOnReference) {
this.newCodeOnReference = newCodeOnReference;
return this;
}

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

private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {

+ 39
- 9
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java Bestand weergeven

@@ -20,7 +20,6 @@
package org.sonar.server.issue.index;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.time.Clock;
import java.time.DateTimeException;
@@ -57,6 +56,7 @@ import org.sonar.server.issue.index.IssueQuery.PeriodStart;
import org.sonar.server.user.UserSession;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Collections2.transform;
import static java.lang.String.format;
import static java.util.Collections.singleton;
@@ -72,6 +72,7 @@ import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
@@ -146,7 +147,7 @@ public class IssueQueryFactory {

setCreatedAfterFromRequest(dbSession, builder, request, allComponents, timeZone);
String sort = request.getSort();
if (!Strings.isNullOrEmpty(sort)) {
if (!isNullOrEmpty(sort)) {
builder.sort(sort);
builder.asc(request.getAsc());
}
@@ -184,6 +185,7 @@ public class IssueQueryFactory {
checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST));
setCreatedAfterFromDates(builder, createdAfter, createdInLast, true);
} else {
// If the filter is on leak period
checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);
checkArgument(createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_IN_LAST, PARAM_SINCE_LEAK_PERIOD));

@@ -191,18 +193,36 @@ public class IssueQueryFactory {
ComponentDto component = componentUuids.iterator().next();

if (!QUALIFIERS_WITHOUT_LEAK_PERIOD.contains(component.qualifier()) && request.getPullRequest() == null) {
Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false);
Optional<SnapshotDto> snapshot = getLastAnalysis(dbSession, component);
boolean isLastAnalysisUsingReferenceBranch = isLastAnalysisUsingReferenceBranch(snapshot);
if (isLastAnalysisUsingReferenceBranch) {
builder.newCodeOnReference(true);
} else {
// if last analysis has no period date, then no issue should be considered new.
Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(snapshot);
setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false);
}
}
}
}

private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) {
Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
// if last analysis has no period date, then no issue should be considered new.
private Date findCreatedAfterFromComponentUuid(Optional<SnapshotDto> snapshot) {
return snapshot.map(s -> longToDate(s.getPeriodDate())).orElseGet(() -> new Date(clock.millis()));
}

private static boolean isLastAnalysisUsingReferenceBranch(Optional<SnapshotDto> snapshot) {
String periodMode = snapshot.map(SnapshotDto::getPeriodMode).orElse("");
return periodMode.equals(REFERENCE_BRANCH.name());
}

private Optional<SnapshotDto> getLastAnalysis(DbSession dbSession, ComponentDto component) {
return dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
}

private List<SnapshotDto> getLastAnalysis(DbSession dbSession, Set<String> projectUuids) {
return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);
}

private boolean mergeDeprecatedComponentParameters(DbSession session, SearchRequest request, List<ComponentDto> allComponents) {
Boolean onComponentOnly = request.getOnComponentOnly();
Collection<String> components = request.getComponents();
@@ -332,12 +352,22 @@ public class IssueQueryFactory {
.flatMap(app -> dbClient.componentDao().selectProjectsFromView(dbSession, app, app).stream())
.collect(toSet());

Map<String, PeriodStart> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
List<SnapshotDto> snapshots = getLastAnalysis(dbSession, projectUuids);

Set<String> newCodeReferenceByProjects = snapshots
.stream()
.filter(s -> !isNullOrEmpty(s.getPeriodMode()) && s.getPeriodMode().equals(REFERENCE_BRANCH.name()))
.map(SnapshotDto::getComponentUuid)
.collect(toSet());

Map<String, PeriodStart> leakByProjects = snapshots
.stream()
.filter(s -> s.getPeriodDate() != null)
.filter(s -> s.getPeriodDate() != null &&
(isNullOrEmpty(s.getPeriodMode()) || !s.getPeriodMode().equals(REFERENCE_BRANCH.name())))
.collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false)));

builder.createdAfterByProjectUuids(leakByProjects);
builder.newCodeOnReferenceByProjectUuids(newCodeReferenceByProjects);
}

private static void addDirectories(IssueQuery.Builder builder, List<ComponentDto> directories) {

+ 57
- 0
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java Bestand weergeven

@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.assertj.core.api.Fail;
import org.elasticsearch.search.SearchHit;
@@ -501,6 +502,51 @@ public class IssueIndexFiltersTest {
project2Branch1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
}

@Test
public void filter_by_new_code_reference_by_projects() {
ComponentDto project1 = newPrivateProjectDto();
IssueDoc project1Issue1 = newDoc(project1).setIsNewCodeReference(true);
IssueDoc project1Issue2 = newDoc(project1).setIsNewCodeReference(false);
ComponentDto project2 = newPrivateProjectDto();
IssueDoc project2Issue1 = newDoc(project2).setIsNewCodeReference(false);
IssueDoc project2Issue2 = newDoc(project2).setIsNewCodeReference(true);
indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);

// Search for issues of project 1 and project 2 that are new code on a branch using reference for new code
assertThatSearchReturnsOnly(IssueQuery.builder()
.newCodeOnReferenceByProjectUuids(Set.of(project1.uuid(), project2.uuid())),
project1Issue1.key(), project2Issue2.key());
}

@Test
public void filter_by_new_code_reference_branches() {
ComponentDto project1 = newPrivateProjectDto();
IssueDoc project1Issue1 = newDoc(project1).setIsNewCodeReference(true);
IssueDoc project1Issue2 = newDoc(project1).setIsNewCodeReference(false);

ComponentDto project1Branch1 = db.components().insertProjectBranch(project1);
IssueDoc project1Branch1Issue1 = newDoc(project1Branch1).setIsNewCodeReference(false);
IssueDoc project1Branch1Issue2 = newDoc(project1Branch1).setIsNewCodeReference(true);

ComponentDto project2 = newPrivateProjectDto();

IssueDoc project2Issue1 = newDoc(project2).setIsNewCodeReference(true);
IssueDoc project2Issue2 = newDoc(project2).setIsNewCodeReference(false);

ComponentDto project2Branch1 = db.components().insertProjectBranch(project2);
IssueDoc project2Branch1Issue1 = newDoc(project2Branch1).setIsNewCodeReference(false);
IssueDoc project2Branch1Issue2 = newDoc(project2Branch1).setIsNewCodeReference(true);

indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2,
project1Branch1Issue1, project1Branch1Issue2, project2Branch1Issue1, project2Branch1Issue2);

// Search for issues of project 1 branch 1 and project 2 branch 1 that are new code on a branch using reference for new code
assertThatSearchReturnsOnly(IssueQuery.builder()
.mainBranch(false)
.newCodeOnReferenceByProjectUuids(Set.of(project1Branch1.uuid(), project2Branch1.uuid())),
project1Branch1Issue2.key(), project2Branch1Issue2.key());
}

@Test
public void filter_by_severities() {
ComponentDto project = newPrivateProjectDto();
@@ -756,6 +802,17 @@ public class IssueIndexFiltersTest {
assertThatSearchReturnsEmpty(IssueQuery.builder().createdAt(parseDate("2014-09-21")));
}

@Test
public void filter_by_new_code_reference() {
ComponentDto project = newPrivateProjectDto();
ComponentDto file = newFileDto(project, null);

indexIssues(newDoc("I1", file).setIsNewCodeReference(true),
newDoc("I2", file).setIsNewCodeReference(false));

assertThatSearchReturnsOnly(IssueQuery.builder().newCodeOnReference(true), "I1");
}

@Test
public void filter_by_cwe() {
ComponentDto project = newPrivateProjectDto();

+ 30
- 1
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java Bestand weergeven

@@ -56,6 +56,7 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
import static org.sonar.db.component.ComponentTesting.newSubPortfolio;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
import static org.sonar.db.rule.RuleTesting.newRule;

public class IssueQueryFactoryTest {
@@ -163,7 +164,29 @@ public class IssueQueryFactoryTest {
assertThat(query.componentUuids()).containsOnly(file.uuid());
assertThat(query.createdAfter().date()).isEqualTo(new Date(leakPeriodStart));
assertThat(query.createdAfter().inclusive()).isFalse();
assertThat(query.newCodeOnReference()).isNull();
}

@Test
public void leak_period_does_not_rely_on_date_for_reference_branch() {
long leakPeriodStart = addDays(new Date(), -14).getTime();

ComponentDto project = db.components().insertPublicProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));

SnapshotDto analysis = db.components().insertSnapshot(project, s -> s.setPeriodMode(REFERENCE_BRANCH.name())
.setPeriodParam("master"));

SearchRequest request = new SearchRequest()
.setComponentUuids(Collections.singletonList(file.uuid()))
.setOnComponentOnly(true)
.setSinceLeakPeriod(true);

IssueQuery query = underTest.create(request);

assertThat(query.componentUuids()).containsOnly(file.uuid());
assertThat(query.newCodeOnReference()).isTrue();
assertThat(query.createdAfter()).isNull();
}

@Test
@@ -319,11 +342,15 @@ public class IssueQueryFactoryTest {
ComponentDto project2 = db.components().insertPublicProject();
db.components().insertSnapshot(project2, s -> s.setPeriodDate(null));
ComponentDto project3 = db.components().insertPublicProject();
ComponentDto project4 = db.components().insertPublicProject();
SnapshotDto analysis2 = db.components().insertSnapshot(project4,
s -> s.setPeriodMode(REFERENCE_BRANCH.name()).setPeriodParam("master"));
ComponentDto application = db.components().insertPublicApplication();
db.components().insertComponents(newProjectCopy("PC1", project1, application));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
db.components().insertComponents(newProjectCopy("PC3", project3, application));
userSession.registerApplication(application, project1, project2, project3);
db.components().insertComponents(newProjectCopy("PC4", project4, application));
userSession.registerApplication(application, project1, project2, project3, project4);

IssueQuery result = underTest.create(new SearchRequest()
.setComponentUuids(singletonList(application.uuid()))
@@ -332,6 +359,8 @@ public class IssueQueryFactoryTest {
assertThat(result.createdAfterByProjectUuids()).hasSize(1);
assertThat(result.createdAfterByProjectUuids().entrySet()).extracting(Map.Entry::getKey, e -> e.getValue().date(), e -> e.getValue().inclusive()).containsOnly(
tuple(project1.uuid(), new Date(analysis1.getPeriodDate()), false));
assertThat(result.newCodeOnReferenceByProjectUuids()).hasSize(1);
assertThat(result.newCodeOnReferenceByProjectUuids()).containsOnly(project4.uuid());
assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
}


+ 59
- 22
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java Bestand weergeven

@@ -19,7 +19,6 @@
*/
package org.sonar.server.hotspot.ws;

import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -37,6 +36,7 @@ import javax.annotation.Nullable;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.jetbrains.annotations.NotNull;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.RuleKey;
@@ -67,6 +67,7 @@ import org.sonarqube.ws.Hotspots.SearchWsResponse;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
@@ -82,7 +83,9 @@ import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.api.utils.Paging.forPageIndex;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
@@ -94,7 +97,7 @@ import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.WsUtils.nullToEmpty;

public class SearchAction implements HotspotsWsAction {
private static final Set<String> SUPPORTED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP);
private static final Set<String> SUPPORTED_QUALIFIERS = Set.of(Qualifiers.PROJECT, Qualifiers.APP);
private static final String PARAM_PROJECT_KEY = "projectKey";
private static final String PARAM_STATUS = "status";
private static final String PARAM_RESOLUTION = "resolution";
@@ -130,7 +133,7 @@ public class SearchAction implements HotspotsWsAction {
}

private static Set<String> setFromList(@Nullable List<String> list) {
return list != null ? ImmutableSet.copyOf(list) : ImmutableSet.of();
return list != null ? Set.copyOf(list) : Set.of();
}

private static WsRequest toWsRequest(Request request) {
@@ -344,24 +347,16 @@ public class SearchAction implements HotspotsWsAction {
if (Qualifiers.APP.equals(project.qualifier())) {
builder.viewUuids(singletonList(projectUuid));
if (wsRequest.isSinceLeakPeriod() && wsRequest.getPullRequest().isEmpty()) {
addCreatedAfterByProjects(builder, dbSession, project);
addSinceLeakPeriodFilterByProjects(builder, dbSession, project);
}
} else {
builder.projectUuids(singletonList(projectUuid));
if (wsRequest.isSinceLeakPeriod() && wsRequest.getPullRequest().isEmpty()) {
var sinceDate = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, project.uuid())
.map(s -> longToDate(s.getPeriodDate()))
.orElseGet(() -> new Date(system2.now()));
builder.createdAfter(sinceDate, false);
addSinceLeakPeriodFilter(dbSession, project, builder);
}
}

if (project.getMainBranchProjectUuid() == null) {
builder.mainBranch(true);
} else {
builder.branchUuid(project.uuid());
builder.mainBranch(false);
}
addMainBranchFilter(project, builder);
}

if (!wsRequest.getHotspotKeys().isEmpty()) {
@@ -379,6 +374,15 @@ public class SearchAction implements HotspotsWsAction {

wsRequest.getStatus().ifPresent(status -> builder.resolved(STATUS_REVIEWED.equals(status)));
wsRequest.getResolution().ifPresent(resolution -> builder.resolutions(singleton(resolution)));
addSecurityStandardFilters(wsRequest, builder);

IssueQuery query = builder.build();
SearchOptions searchOptions = new SearchOptions()
.setPage(wsRequest.page, wsRequest.index);
return issueIndex.search(query, searchOptions);
}

private static void addSecurityStandardFilters(WsRequest wsRequest, IssueQuery.Builder builder) {
if (!wsRequest.getOwaspTop10().isEmpty()) {
builder.owaspTop10(wsRequest.getOwaspTop10());
}
@@ -388,18 +392,38 @@ public class SearchAction implements HotspotsWsAction {
if (!wsRequest.getSonarsourceSecurity().isEmpty()) {
builder.sonarsourceSecurity(wsRequest.getSonarsourceSecurity());
}

if (!wsRequest.getCwe().isEmpty()) {
builder.cwe(wsRequest.getCwe());
}
}

IssueQuery query = builder.build();
SearchOptions searchOptions = new SearchOptions()
.setPage(wsRequest.page, wsRequest.index);
return issueIndex.search(query, searchOptions);
private static void addMainBranchFilter(@NotNull ComponentDto project, IssueQuery.Builder builder) {
if (project.getMainBranchProjectUuid() == null) {
builder.mainBranch(true);
} else {
builder.branchUuid(project.uuid());
builder.mainBranch(false);
}
}

private void addSinceLeakPeriodFilter(DbSession dbSession, @NotNull ComponentDto project, IssueQuery.Builder builder) {
Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, project.uuid());

boolean isLastAnalysisUsingReferenceBranch = snapshot.map(SnapshotDto::getPeriodMode)
.orElse("").equals(REFERENCE_BRANCH.name());

if (isLastAnalysisUsingReferenceBranch) {
builder.newCodeOnReference(true);
} else {
var sinceDate = snapshot
.map(s -> longToDate(s.getPeriodDate()))
.orElseGet(() -> new Date(system2.now()));

builder.createdAfter(sinceDate, false);
}
}

private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, ComponentDto application) {
private void addSinceLeakPeriodFilterByProjects(IssueQuery.Builder builder, DbSession dbSession, ComponentDto application) {
Set<String> projectUuids;
if (application.getMainBranchProjectUuid() == null) {
projectUuids = dbClient.applicationProjectsDao().selectProjects(dbSession, application.uuid()).stream()
@@ -412,10 +436,23 @@ public class SearchAction implements HotspotsWsAction {
}

long now = system2.now();
Map<String, IssueQuery.PeriodStart> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids).stream()
.collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new IssueQuery.PeriodStart(longToDate(s.getPeriodDate() == null ? now : s.getPeriodDate()), false)));

List<SnapshotDto> snapshots = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);

Set<String> newCodeReferenceByProjects = snapshots
.stream()
.filter(s -> !isNullOrEmpty(s.getPeriodMode()) && s.getPeriodMode().equals(REFERENCE_BRANCH.name()))
.map(SnapshotDto::getComponentUuid)
.collect(toSet());

Map<String, IssueQuery.PeriodStart> leakByProjects = snapshots
.stream()
.filter(s -> isNullOrEmpty(s.getPeriodMode()) || !s.getPeriodMode().equals(REFERENCE_BRANCH.name()))
.collect(uniqueIndex(SnapshotDto::getComponentUuid, s ->
new IssueQuery.PeriodStart(longToDate(s.getPeriodDate() == null ? now : s.getPeriodDate()), false)));

builder.createdAfterByProjectUuids(leakByProjects);
builder.newCodeOnReferenceByProjectUuids(newCodeReferenceByProjects);
}

private void loadComponents(DbSession dbSession, SearchResponseData searchResponseData) {

+ 43
- 0
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java Bestand weergeven

@@ -99,7 +99,9 @@ import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
import static org.sonar.db.issue.IssueTesting.newIssue;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;

@RunWith(DataProviderRunner.class)
public class SearchActionTest {
@@ -1484,6 +1486,47 @@ public class SearchActionTest {
.toArray(String[]::new));
}

@Test
public void returns_hotspots_on_the_leak_period_when_sinceLeakPeriod_is_true_and_branch_uses_reference_branch() {
ComponentDto project = dbTester.components().insertPublicProject();
userSessionRule.registerComponents(project);
indexPermissions();
ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
dbTester.components().insertSnapshot(project, t -> t.setPeriodMode(REFERENCE_BRANCH.name()).setPeriodParam("master"));
RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
List<IssueDto> hotspotsInLeakPeriod = IntStream.range(0, 1 + RANDOM.nextInt(20))
.mapToObj(i -> dbTester.issues().insertHotspot(rule, project, file, t -> t.setLine(i)))
.collect(toList());

hotspotsInLeakPeriod.stream().forEach(i -> dbTester.issues().insertNewCodeReferenceIssue(newCodeReferenceIssue(i)));

List<IssueDto> hotspotsNotInLeakPeriod = IntStream.range(0, 1 + RANDOM.nextInt(20))
.mapToObj(i -> dbTester.issues().insertHotspot(rule, project, file, t -> t.setLine(i)))
.collect(toList());
indexIssues();

SearchWsResponse responseAll = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(responseAll.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(Stream.of(
hotspotsInLeakPeriod.stream(),
hotspotsNotInLeakPeriod.stream())
.flatMap(t -> t)
.map(IssueDto::getKey)
.toArray(String[]::new));

SearchWsResponse responseOnLeak = newRequest(project,
t -> t.setParam("sinceLeakPeriod", "true"))
.executeProtobuf(SearchWsResponse.class);
assertThat(responseOnLeak.getHotspotsList())
.extracting(SearchWsResponse.Hotspot::getKey)
.containsExactlyInAnyOrder(hotspotsInLeakPeriod
.stream()
.map(IssueDto::getKey)
.toArray(String[]::new));
}

@Test
public void returns_nothing_when_sinceLeakPeriod_is_true_and_no_period_exists() {
long referenceDate = 800_996_999_332L;

Laden…
Annuleren
Opslaan