Browse Source

SONAR-9616 Support branch in api/issues/search

tags/6.6-RC1
Julien Lancelot 6 years ago
parent
commit
47ef3bd0dc
19 changed files with 452 additions and 146 deletions
  1. 8
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
  2. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
  3. 12
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
  4. 18
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
  5. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java
  6. 13
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java
  7. 73
    71
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java
  8. 4
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  9. 10
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  10. 4
    2
      server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java
  11. 19
    12
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java
  12. 95
    14
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java
  13. 7
    0
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java
  14. 74
    22
      server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
  15. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
  16. 89
    18
      server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java
  17. 10
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
  18. 1
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
  19. 11
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java

+ 8
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java View File

@@ -35,6 +35,7 @@ import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
import org.sonar.db.RowNotFoundException;
@@ -47,6 +48,7 @@ import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.DatabaseUtils.executeLargeInputs;
import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
import static org.sonar.db.component.ComponentDto.generateBranchKey;

public class ComponentDao implements Dao {

@@ -169,6 +171,11 @@ public class ComponentDao implements Dao {
return executeLargeInputs(keys, mapper(session)::selectByKeys);
}

public List<ComponentDto> selectByKeysAndBranch(DbSession session, Collection<String> keys, String branch) {
List<String> dbKeys = keys.stream().map(k -> generateBranchKey(k, branch)).collect(MoreCollectors.toList());
return executeLargeInputs(dbKeys, mapper(session)::selectByDbKeys);
}

public List<ComponentDto> selectComponentsHavingSameKeyOrderedById(DbSession session, String key) {
return mapper(session).selectComponentsHavingSameKeyOrderedById(key);
}
@@ -215,7 +222,7 @@ public class ComponentDao implements Dao {
}

public java.util.Optional<ComponentDto> selectByKeyAndBranch(DbSession session, String key, String branch) {
return java.util.Optional.ofNullable(mapper(session).selectByDbKey(ComponentDto.generateBranchKey(key, branch)));
return java.util.Optional.ofNullable(mapper(session).selectByDbKey(generateBranchKey(key, branch)));
}

public List<UuidWithProjectUuidDto> selectAllViewsAndSubViews(DbSession session) {

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java View File

@@ -52,6 +52,8 @@ public interface ComponentMapper {

List<ComponentDto> selectByKeys(@Param("keys") Collection<String> keys);

List<ComponentDto> selectByDbKeys(@Param("keys") Collection<String> keys);

List<ComponentDto> selectByIds(@Param("ids") Collection<Long> ids);

List<ComponentDto> selectByUuids(@Param("uuids") Collection<String> uuids);

+ 12
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml View File

@@ -89,6 +89,18 @@
</foreach>
</select>

<select id="selectByDbKeys" parameterType="String" resultType="Component">
select
<include refid="componentColumns"/>
from projects p
where
p.enabled=${_true}
and p.kee in
<foreach collection="keys" open="(" close=")" item="key" separator=",">
#{key,jdbcType=VARCHAR}
</foreach>
</select>

<select id="selectByIds" parameterType="long" resultType="Component">
select
<include refid="componentColumns"/>

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

@@ -249,6 +249,24 @@ public class ComponentDaoTest {
assertThat(underTest.selectByKeys(dbSession, singletonList("unknown"))).isEmpty();
}

@Test
public void selectByKeysAndBranch() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
ComponentDto file1 = db.components().insertComponent(newFileDto(branch));
ComponentDto file2 = db.components().insertComponent(newFileDto(branch));
ComponentDto anotherBranch = db.components().insertProjectBranch(project, b -> b.setKey("another_branch"));
ComponentDto fileOnAnotherBranch = db.components().insertComponent(newFileDto(anotherBranch));

assertThat(underTest.selectByKeysAndBranch(dbSession, asList(branch.getKey(), file1.getKey(), file2.getKey()), "my_branch")).extracting(ComponentDto::uuid)
.containsExactlyInAnyOrder(branch.uuid(), file1.uuid(), file2.uuid());
assertThat(underTest.selectByKeysAndBranch(dbSession, asList(file1.getKey(), file2.getKey(), fileOnAnotherBranch.getKey()), "my_branch")).extracting(ComponentDto::uuid)
.containsExactlyInAnyOrder(file1.uuid(), file2.uuid());
assertThat(underTest.selectByKeysAndBranch(dbSession, singletonList(fileOnAnotherBranch.getKey()), "my_branch")).isEmpty();
assertThat(underTest.selectByKeysAndBranch(dbSession, singletonList(file1.getKey()), "unknown")).isEmpty();
assertThat(underTest.selectByKeysAndBranch(dbSession, singletonList("unknown"), "my_branch")).isEmpty();
}

@Test
public void get_by_ids() {
ComponentDto project1 = db.components().insertPrivateProject();

+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java View File

@@ -205,7 +205,7 @@ public class ComponentDbTester {
public final ComponentDto insertProjectBranch(ComponentDto project, Consumer<BranchDto>... dtoPopulators) {
String uuid = Uuids.createFast();
BranchDto branchDto = new BranchDto()
.setKey(randomAlphanumeric(255))
.setKey("branch_" + randomAlphanumeric(248))
.setUuid(uuid)
.setProjectUuid(project.uuid())
.setKeeType(BRANCH)

+ 13
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java View File

@@ -81,6 +81,7 @@ public class IssueQuery {
private final Boolean asc;
private final String facetMode;
private final String organizationUuid;
private final String branchUuid;
private final boolean checkAuthorization;

private IssueQuery(Builder builder) {
@@ -113,6 +114,7 @@ public class IssueQuery {
this.checkAuthorization = builder.checkAuthorization;
this.facetMode = builder.facetMode;
this.organizationUuid = builder.organizationUuid;
this.branchUuid = builder.branchUuid;
}

public Collection<String> issueKeys() {
@@ -236,6 +238,11 @@ public class IssueQuery {
return organizationUuid;
}

@CheckForNull
public String branchUuid() {
return branchUuid;
}

public String facetMode() {
return facetMode;
}
@@ -279,6 +286,7 @@ public class IssueQuery {
private boolean checkAuthorization = true;
private String facetMode;
private String organizationUuid;
private String branchUuid;

private Builder() {

@@ -450,6 +458,11 @@ public class IssueQuery {
this.organizationUuid = s;
return this;
}

public Builder branchUuid(@Nullable String s) {
this.branchUuid = s;
return this;
}
}

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

+ 73
- 71
server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java View File

@@ -25,6 +25,7 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -46,7 +47,6 @@ import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
@@ -64,8 +64,10 @@ import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
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.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
@@ -84,6 +86,9 @@ public class IssueQueryFactory {
public static final String LOGIN_MYSELF = "__me__";

private static final String UNKNOWN = "<UNKNOWN>";

private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN);

private final DbClient dbClient;
private final System2 system;
private final UserSession userSession;
@@ -113,30 +118,17 @@ public class IssueQueryFactory {
.facetMode(request.getFacetMode())
.organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));

Set<String> allComponentUuids = new HashSet<>();
boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession,
request.getOnComponentOnly(),
request.getComponents(),
request.getComponentUuids(),
request.getComponentKeys(),
request.getComponentRootUuids(),
request.getComponentRoots(),
allComponentUuids);

addComponentParameters(builder, dbSession,
effectiveOnComponentOnly,
allComponentUuids,
request);

builder.createdAfter(buildCreatedAfterFromRequest(dbSession, request, allComponentUuids));
List<ComponentDto> allComponents = new ArrayList<>();
boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);

builder.createdAfter(buildCreatedAfterFromRequest(dbSession, request, allComponents));
String sort = request.getSort();
if (!Strings.isNullOrEmpty(sort)) {
builder.sort(sort);
builder.asc(request.getAsc());
}
return builder.build();

}
}

@@ -161,7 +153,7 @@ public class IssueQueryFactory {
return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
}

private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set<String> componentUuids) {
private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, List<ComponentDto> componentUuids) {
Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
String createdInLast = request.getCreatedInLast();

@@ -170,16 +162,14 @@ public class IssueQueryFactory {
}

checkRequest(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD);

checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period");
String uuid = componentUuids.iterator().next();
Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, uuid);
ComponentDto component = componentUuids.iterator().next();
Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component);
return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast);
}

@CheckForNull
private Date findCreatedAfterFromComponentUuid(DbSession dbSession, String uuid) {
ComponentDto component = checkFoundWithOptional(dbClient.componentDao().selectByUuid(dbSession, uuid), "Component with id '%s' not found", uuid);
private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) {
Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid());
return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null);
}
@@ -200,13 +190,15 @@ public class IssueQueryFactory {
return assignees;
}

private boolean mergeDeprecatedComponentParameters(DbSession session, @Nullable Boolean onComponentOnly,
@Nullable Collection<String> components,
@Nullable Collection<String> componentUuids,
@Nullable Collection<String> componentKeys,
@Nullable Collection<String> componentRootUuids,
@Nullable Collection<String> componentRoots,
Set<String> allComponentUuids) {
private boolean mergeDeprecatedComponentParameters(DbSession session, SearchWsRequest request, List<ComponentDto> allComponents) {
Boolean onComponentOnly = request.getOnComponentOnly();
Collection<String> components = request.getComponents();
Collection<String> componentUuids = request.getComponentUuids();
Collection<String> componentKeys = request.getComponentKeys();
Collection<String> componentRootUuids = request.getComponentRootUuids();
Collection<String> componentRoots = request.getComponentRoots();
String branch = request.getBranch();

boolean effectiveOnComponentOnly = false;

checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots),
@@ -214,19 +206,22 @@ public class IssueQueryFactory {
PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS, PARAM_COMPONENTS, PARAM_COMPONENT_ROOTS, PARAM_COMPONENT_UUIDS);

if (componentRootUuids != null) {
allComponentUuids.addAll(componentRootUuids);
allComponents.addAll(getComponentsFromUuids(session, componentRootUuids));
effectiveOnComponentOnly = false;
} else if (componentRoots != null) {
allComponentUuids.addAll(convertComponentKeysToUuids(session, componentRoots));
allComponents.addAll(getComponentsFromKeys(session, componentRoots, branch));
effectiveOnComponentOnly = false;
} else if (components != null) {
allComponentUuids.addAll(convertComponentKeysToUuids(session, components));
allComponents.addAll(getComponentsFromKeys(session, components, branch));
effectiveOnComponentOnly = true;
} else if (componentUuids != null) {
allComponentUuids.addAll(componentUuids);
allComponents.addAll(getComponentsFromUuids(session, componentUuids));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
} else if (componentKeys != null) {
allComponentUuids.addAll(convertComponentKeysToUuids(session, componentKeys));
allComponents.addAll(getComponentsFromKeys(session, componentKeys, branch));
effectiveOnComponentOnly = BooleanUtils.isTrue(onComponentOnly);
}

return effectiveOnComponentOnly;
}

@@ -236,14 +231,14 @@ public class IssueQueryFactory {
.count() <= 1;
}

private void addComponentParameters(IssueQuery.Builder builder, DbSession session,
boolean onComponentOnly,
Collection<String> componentUuids,
SearchWsRequest request) {
private void addComponentParameters(IssueQuery.Builder builder, DbSession session, boolean onComponentOnly,
List<ComponentDto> components, SearchWsRequest request) {

String branch = request.getBranch();
builder.onComponentOnly(onComponentOnly);
if (onComponentOnly) {
builder.componentUuids(componentUuids);
builder.componentUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
builder.branchUuid(branch == null ? null : components.get(0).projectUuid());
return;
}

@@ -254,64 +249,63 @@ public class IssueQueryFactory {
if (projectUuids != null) {
builder.projectUuids(projectUuids);
} else if (projectKeys != null) {
builder.projectUuids(convertComponentKeysToUuids(session, projectKeys));
List<ComponentDto> projects = getComponentsFromKeys(session, projectKeys, branch);
builder.projectUuids(projects.stream().map(p -> branch == null ? p.projectUuid() : p.getMainBranchProjectUuid()).collect(toList()));
builder.branchUuid(branch == null ? null : projects.get(0).projectUuid());
}
builder.moduleUuids(request.getModuleUuids());
builder.directories(request.getDirectories());
builder.fileUuids(request.getFileUuids());

if (!componentUuids.isEmpty()) {
addComponentsBasedOnQualifier(builder, session, componentUuids, request);
}
addComponentsBasedOnQualifier(builder, session, components, request);
}

private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, Collection<String> componentUuids, SearchWsRequest request) {
if (componentUuids.isEmpty()) {
builder.componentUuids(componentUuids);
private void addComponentsBasedOnQualifier(IssueQuery.Builder builder, DbSession dbSession, List<ComponentDto> components, SearchWsRequest request) {
if (components.isEmpty()) {
return;
}

List<ComponentDto> components = dbClient.componentDao().selectByUuids(dbSession, componentUuids);
if (components.isEmpty()) {
builder.componentUuids(componentUuids);
if (components.stream().map(ComponentDto::uuid).anyMatch(uuid -> uuid.equals(UNKNOWN))) {
builder.componentUuids(singleton(UNKNOWN));
return;
}

Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(MoreCollectors.toHashSet());
Set<String> qualifiers = components.stream().map(ComponentDto::qualifier).collect(toHashSet());
checkArgument(qualifiers.size() == 1, "All components must have the same qualifier, found %s", String.join(",", qualifiers));

String branch = request.getBranch();
builder.branchUuid(branch == null ? null : components.get(0).projectUuid());
String qualifier = qualifiers.iterator().next();
switch (qualifier) {
case Qualifiers.VIEW:
case Qualifiers.SUBVIEW:
addViewsOrSubViews(builder, componentUuids);
addViewsOrSubViews(builder, components);
break;
case Qualifiers.APP:
addApplications(builder, dbSession, components, request);
break;
case Qualifiers.PROJECT:
builder.projectUuids(componentUuids);
builder.projectUuids(components.stream().map(c -> branch == null ? c.projectUuid() : c.getMainBranchProjectUuid()).collect(toList()));
break;
case Qualifiers.MODULE:
builder.moduleRootUuids(componentUuids);
builder.moduleRootUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
break;
case Qualifiers.DIRECTORY:
addDirectories(builder, components);
break;
case Qualifiers.FILE:
case Qualifiers.UNIT_TEST_FILE:
builder.fileUuids(componentUuids);
builder.fileUuids(components.stream().map(ComponentDto::uuid).collect(toList()));
break;
default:
throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(componentUuids));
throw new IllegalArgumentException("Unable to set search root context for components " + Joiner.on(',').join(components));
}
}

private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<String> viewOrSubViewUuids) {
private void addViewsOrSubViews(IssueQuery.Builder builder, Collection<ComponentDto> viewOrSubViewUuids) {
List<String> filteredViewUuids = viewOrSubViewUuids.stream()
.filter(uuid -> userSession.hasComponentUuidPermission(UserRole.USER, uuid))
.filter(uuid -> userSession.hasComponentPermission(UserRole.USER, uuid))
.map(ComponentDto::uuid)
.collect(Collectors.toList());

if (filteredViewUuids.isEmpty()) {
filteredViewUuids.add(UNKNOWN);
}
@@ -322,7 +316,7 @@ public class IssueQueryFactory {
Set<String> authorizedApplicationUuids = applications.stream()
.filter(app -> userSession.hasComponentPermission(UserRole.USER, app))
.map(ComponentDto::uuid)
.collect(MoreCollectors.toSet());
.collect(toSet());

builder.viewUuids(authorizedApplicationUuids.isEmpty() ? singleton(UNKNOWN) : authorizedApplicationUuids);
addCreatedAfterByProjects(builder, dbSession, request, authorizedApplicationUuids);
@@ -335,7 +329,7 @@ public class IssueQueryFactory {

Set<String> projectUuids = applicationUuids.stream()
.flatMap(app -> dbClient.componentDao().selectProjectsFromView(dbSession, app, app).stream())
.collect(MoreCollectors.toSet());
.collect(toSet());

Map<String, Date> leakByProjects = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids)
.stream()
@@ -355,14 +349,22 @@ public class IssueQueryFactory {
builder.directories(directoryPaths);
}

private Collection<String> convertComponentKeysToUuids(DbSession dbSession, Collection<String> componentKeys) {
List<String> componentUuids = dbClient.componentDao().selectByKeys(dbSession, componentKeys).stream().map(ComponentDto::uuid).collect(MoreCollectors.toList());
// If unknown components are given, but no components are found, then all issues will be returned,
// so we add this hack in order to return no issue in this case.
if (!componentKeys.isEmpty() && componentUuids.isEmpty()) {
return singletonList(UNKNOWN);
private List<ComponentDto> getComponentsFromKeys(DbSession dbSession, Collection<String> componentKeys, @Nullable String branch) {
List<ComponentDto> componentDtos = branch == null
? dbClient.componentDao().selectByKeys(dbSession, componentKeys)
: dbClient.componentDao().selectByKeysAndBranch(dbSession, componentKeys, branch);
if (!componentKeys.isEmpty() && componentDtos.isEmpty()) {
return singletonList(UNKNOWN_COMPONENT);
}
return componentDtos;
}

private List<ComponentDto> getComponentsFromUuids(DbSession dbSession, Collection<String> componentUuids) {
List<ComponentDto> componentDtos = dbClient.componentDao().selectByUuids(dbSession, componentUuids);
if (!componentUuids.isEmpty() && componentDtos.isEmpty()) {
return singletonList(UNKNOWN_COMPONENT);
}
return componentUuids;
return componentDtos;
}

@VisibleForTesting

+ 4
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

@@ -76,8 +76,8 @@ import org.sonar.server.permission.index.AuthorizationTypeSupport;
import org.sonar.server.user.UserSession;
import org.sonar.server.view.index.ViewIndexDefinition;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
@@ -205,12 +205,15 @@ public class IssueIndex {
QueryBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids());
QueryBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories());
QueryBuilder fileFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids());
QueryBuilder branchFilter = createTermFilter(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, query.branchUuid());
filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.branchUuid() == null)));

if (BooleanUtils.isTrue(query.onComponentOnly())) {
filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentFilter);
} else {
filters.put("__view", viewFilter);
filters.put(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectFilter);
filters.put(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchFilter);
filters.put("__module", moduleRootFilter);
filters.put(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleFilter);
filters.put(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryFilter);

+ 10
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java View File

@@ -57,6 +57,7 @@ import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.sonar.api.utils.Paging.forPageIndex;
import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH;
@@ -71,6 +72,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASC;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNED;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHORS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_ROOTS;
@@ -113,7 +115,7 @@ public class SearchAction implements IssuesWsAction {
private final SearchResponseFormat searchResponseFormat;

public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory,
SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat) {
SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat) {
this.userSession = userSession;
this.issueIndex = issueIndex;
this.issueQueryFactory = issueQueryFactory;
@@ -288,6 +290,12 @@ public class SearchAction implements IssuesWsAction {
.setInternal(true)
.setExampleValue("bdd82933-3070-4903-9188-7d8749e8bb92");

action.createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001)
.setInternal(true)
.setSince("6.6");

action.createParam(PARAM_ORGANIZATION)
.setDescription("Organization key")
.setRequired(false)
@@ -465,6 +473,7 @@ public class SearchAction implements IssuesWsAction {
.setLanguages(request.paramAsStrings(PARAM_LANGUAGES))
.setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS))
.setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY))
.setBranch(request.param(PARAM_BRANCH))
.setOrganization(request.param(PARAM_ORGANIZATION))
.setPage(request.mandatoryParamAsInt(Param.PAGE))
.setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))

+ 4
- 2
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java View File

@@ -33,11 +33,13 @@ import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.source.FileSourceDto;
import org.sonar.scanner.protocol.input.FileData;
import org.sonar.scanner.protocol.input.ProjectRepositories;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
@@ -64,8 +66,8 @@ public class ProjectDataLoaderTest {
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
private int uuidCounter = 0;
private ProjectDataLoader underTest = new ProjectDataLoader(dbClient, userSession);
private ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT);
private ProjectDataLoader underTest = new ProjectDataLoader(dbClient, userSession, new ComponentFinder(dbClient, resourceTypes));

@Test
public void return_project_settings_with_global_scan_permission() {

+ 19
- 12
server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java View File

@@ -36,6 +36,25 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN;

public class IssueDocTesting {

public static IssueDoc newDoc(ComponentDto componentDto) {
return newDoc(Uuids.createFast(), componentDto);
}

public static IssueDoc newDoc(String key, ComponentDto componentDto) {
String mainBranchProjectUuid = componentDto.getMainBranchProjectUuid();
return newDoc()
.setKey(key)
.setBranchUuid(componentDto.projectUuid())
.setComponentUuid(componentDto.uuid())
.setModuleUuid(!componentDto.scope().equals(Scopes.PROJECT) ? componentDto.moduleUuid() : componentDto.uuid())
.setModuleUuidPath(componentDto.moduleUuidPath())
.setProjectUuid(mainBranchProjectUuid == null ? componentDto.projectUuid() : mainBranchProjectUuid)
.setOrganizationUuid(componentDto.getOrganizationUuid())
// File path make no sens on modules and projects
.setFilePath(!componentDto.scope().equals(Scopes.PROJECT) ? componentDto.path() : null)
.setIsMainBranch(mainBranchProjectUuid == null);
}

public static IssueDoc newDoc() {
IssueDoc doc = new IssueDoc(Maps.<String, Object>newHashMap());
doc.setKey(Uuids.createFast());
@@ -60,16 +79,4 @@ public class IssueDocTesting {
doc.setFuncCloseDate(null);
return doc;
}

public static IssueDoc newDoc(String key, ComponentDto componentDto) {
return newDoc()
.setKey(key)
.setComponentUuid(componentDto.uuid())
.setModuleUuid(!componentDto.scope().equals(Scopes.PROJECT) ? componentDto.moduleUuid() : componentDto.uuid())
.setModuleUuidPath(componentDto.moduleUuidPath())
.setProjectUuid(componentDto.projectUuid())
.setOrganizationUuid(componentDto.getOrganizationUuid())
// File path make no sens on modules and projects
.setFilePath(!componentDto.scope().equals(Scopes.PROJECT) ? componentDto.path() : null);
}
}

+ 95
- 14
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java View File

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

import java.util.ArrayList;
import java.util.Date;
import org.junit.Rule;
import org.junit.Test;
@@ -29,14 +30,12 @@ import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.tester.UserSessionRule;
import org.sonarqube.ws.client.issue.SearchWsRequest;

import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -45,7 +44,11 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.DateUtils.addDays;
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.component.ComponentTesting.newProjectCopy;
import static org.sonar.db.component.ComponentTesting.newSubView;

public class IssueQueryFactoryTest {

@@ -63,8 +66,8 @@ public class IssueQueryFactoryTest {
public void create_from_parameters() {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(organization);
ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
ComponentDto module = db.components().insertComponent(newModuleDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(project));

SearchWsRequest request = new SearchWsRequest()
.setIssues(asList("anIssueKey"))
@@ -133,6 +136,23 @@ public class IssueQueryFactoryTest {
assertThat(query.componentUuids()).containsOnly("<UNKNOWN>");
}

@Test
public void query_without_any_parameter() {
SearchWsRequest request = new SearchWsRequest();

IssueQuery query = underTest.create(request);

assertThat(query.componentUuids()).isEmpty();
assertThat(query.projectUuids()).isEmpty();
assertThat(query.moduleUuids()).isEmpty();
assertThat(query.moduleRootUuids()).isEmpty();
assertThat(query.directories()).isEmpty();
assertThat(query.fileUuids()).isEmpty();
assertThat(query.viewUuids()).isEmpty();
assertThat(query.organizationUuid()).isNull();
assertThat(query.branchUuid()).isNull();
}

@Test
public void parse_list_of_rules() {
assertThat(IssueQueryFactory.toRules(null)).isNull();
@@ -181,7 +201,7 @@ public class IssueQueryFactoryTest {
@Test
public void fail_if_componentRoots_references_components_with_different_qualifier() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(project));
SearchWsRequest request = new SearchWsRequest()
.setComponentRoots(asList(project.getDbKey(), file.getDbKey()));

@@ -234,18 +254,17 @@ public class IssueQueryFactoryTest {

IssueQuery result = underTest.create(new SearchWsRequest()
.setComponentUuids(singletonList(application.uuid()))
.setSinceLeakPeriod(true)
);
.setSinceLeakPeriod(true));

assertThat(result.createdAfterByProjectUuids()).containsOnly(
entry(project1.uuid(), new Date(analysis1.getPeriodDate())));
entry(project1.uuid(), new Date(analysis1.getPeriodDate())));
assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid());
}

@Test
public void return_empty_results_if_not_allowed_to_search_for_subview() {
ComponentDto view = db.components().insertView();
ComponentDto subView = db.components().insertComponent(ComponentTesting.newSubView(view));
ComponentDto subView = db.components().insertComponent(newSubView(view));
SearchWsRequest request = new SearchWsRequest()
.setComponentRootUuids(asList(subView.uuid()));

@@ -282,7 +301,7 @@ public class IssueQueryFactoryTest {
@Test
public void should_search_in_tree_with_module_uuid() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
ComponentDto module = db.components().insertComponent(newModuleDto(project));
SearchWsRequest request = new SearchWsRequest()
.setComponentUuids(asList(module.uuid()));

@@ -294,7 +313,7 @@ public class IssueQueryFactoryTest {
@Test
public void param_componentUuids_enables_search_in_directory_tree() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
ComponentDto dir = db.components().insertComponent(newDirectory(project, "src/main/java/foo"));
SearchWsRequest request = new SearchWsRequest()
.setComponentUuids(asList(dir.uuid()));

@@ -308,7 +327,7 @@ public class IssueQueryFactoryTest {
@Test
public void param_componentUuids_enables_search_by_file() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
ComponentDto file = db.components().insertComponent(newFileDto(project));
SearchWsRequest request = new SearchWsRequest()
.setComponentUuids(asList(file.uuid()));

@@ -320,7 +339,7 @@ public class IssueQueryFactoryTest {
@Test
public void param_componentUuids_enables_search_by_test_file() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
ComponentDto file = db.components().insertComponent(newFileDto(project).setQualifier(Qualifiers.UNIT_TEST_FILE));
SearchWsRequest request = new SearchWsRequest()
.setComponentUuids(asList(file.uuid()));

@@ -329,6 +348,65 @@ public class IssueQueryFactoryTest {
assertThat(query.fileUuids()).containsExactly(file.uuid());
}

@Test
public void search_issue_from_branch() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project);

assertThat(underTest.create(new SearchWsRequest()
.setProjectKeys(singletonList(branch.getKey()))
.setBranch(branch.getBranch())))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()))
.containsOnly(branch.uuid(), singletonList(project.uuid()));

assertThat(underTest.create(new SearchWsRequest()
.setComponentKeys(singletonList(branch.getKey()))
.setBranch(branch.getBranch())))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()))
.containsOnly(branch.uuid(), singletonList(project.uuid()));
}

@Test
public void search_file_issue_from_branch() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto file = db.components().insertComponent(newFileDto(branch));

assertThat(underTest.create(new SearchWsRequest()
.setComponentKeys(singletonList(file.getKey()))
.setBranch(branch.getBranch())))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()))
.containsOnly(branch.uuid(), singletonList(file.uuid()));

assertThat(underTest.create(new SearchWsRequest()
.setComponentKeys(singletonList(branch.getKey()))
.setFileUuids(singletonList(file.uuid()))
.setBranch(branch.getBranch())))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()))
.containsOnly(branch.uuid(), singletonList(file.uuid()));

assertThat(underTest.create(new SearchWsRequest()
.setProjectKeys(singletonList(branch.getKey()))
.setFileUuids(singletonList(file.uuid()))
.setBranch(branch.getBranch())))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()))
.containsOnly(branch.uuid(), singletonList(file.uuid()));
}

@Test
public void search_issue_on_component_only_from_branch() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto file = db.components().insertComponent(newFileDto(branch));

assertThat(underTest.create(new SearchWsRequest()
.setComponentKeys(singletonList(file.getKey()))
.setBranch(branch.getBranch())
.setOnComponentOnly(true)))
.extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()))
.containsOnly(branch.uuid(), singletonList(file.uuid()));
}

@Test
public void fail_if_created_after_and_created_since_are_both_set() {
SearchWsRequest request = new SearchWsRequest()
@@ -372,12 +450,15 @@ public class IssueQueryFactoryTest {

@Test
public void fail_if_several_components_provided_with_since_leak_period() {
ComponentDto project1 = db.components().insertPrivateProject();
ComponentDto project2 = db.components().insertPrivateProject();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("One and only one component must be provided when searching since leak period");

underTest.create(new SearchWsRequest()
.setSinceLeakPeriod(true)
.setComponentUuids(newArrayList("foo", "bar")));
.setComponentKeys(asList(project1.getKey(), project2.getKey())));
}

@Test

+ 7
- 0
server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java View File

@@ -48,6 +48,8 @@ public class IssueQueryTest {
.languages(newArrayList("xoo"))
.tags(newArrayList("tag1", "tag2"))
.types(newArrayList("RELIABILITY", "SECURITY"))
.organizationUuid("orga")
.branchUuid("my_branch")
.createdAfterByProjectUuids(ImmutableMap.of("PROJECT", new Date(10_000_000_000L)))
.assigned(true)
.createdAfter(new Date())
@@ -68,6 +70,8 @@ public class IssueQueryTest {
assertThat(query.languages()).containsOnly("xoo");
assertThat(query.tags()).containsOnly("tag1", "tag2");
assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
assertThat(query.organizationUuid()).isEqualTo("orga");
assertThat(query.branchUuid()).isEqualTo("my_branch");
assertThat(query.createdAfterByProjectUuids()).containsOnly(entry("PROJECT", new Date(10_000_000_000L)));
assertThat(query.assigned()).isTrue();
assertThat(query.rules()).containsOnly(RuleKey.of("squid", "AvoidCycle"));
@@ -151,6 +155,9 @@ public class IssueQueryTest {
assertThat(query.severities()).isEmpty();
assertThat(query.languages()).isEmpty();
assertThat(query.tags()).isEmpty();
assertThat(query.types()).isEmpty();
assertThat(query.organizationUuid()).isNull();
assertThat(query.branchUuid()).isNull();
assertThat(query.assigned()).isNull();
assertThat(query.createdAfter()).isNull();
assertThat(query.createdBefore()).isNull();

+ 74
- 22
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java View File

@@ -74,6 +74,7 @@ import static org.sonar.api.utils.DateUtils.addDays;
import static org.sonar.api.utils.DateUtils.parseDate;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
import static org.sonar.db.user.GroupTesting.newGroupDto;
@@ -125,8 +126,8 @@ public class IssueIndexTest {
@Test
public void filter_by_projects() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
ComponentDto module = ComponentTesting.newModuleDto(project);
ComponentDto subModule = ComponentTesting.newModuleDto(module);
ComponentDto module = newModuleDto(project);
ComponentDto subModule = newModuleDto(module);

indexIssues(
newDoc("I1", project),
@@ -157,8 +158,8 @@ public class IssueIndexTest {
@Test
public void filter_by_modules() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
ComponentDto module = ComponentTesting.newModuleDto(project);
ComponentDto subModule = ComponentTesting.newModuleDto(module);
ComponentDto module = newModuleDto(project);
ComponentDto subModule = newModuleDto(module);
ComponentDto file = newFileDto(subModule, null);

indexIssues(
@@ -176,8 +177,8 @@ public class IssueIndexTest {
@Test
public void filter_by_components_on_contextualized_search() {
ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto());
ComponentDto module = ComponentTesting.newModuleDto(project);
ComponentDto subModule = ComponentTesting.newModuleDto(module);
ComponentDto module = newModuleDto(project);
ComponentDto subModule = newModuleDto(module);
ComponentDto file1 = newFileDto(project, null);
ComponentDto file2 = newFileDto(module, null);
ComponentDto file3 = newFileDto(subModule, null);
@@ -205,9 +206,9 @@ public class IssueIndexTest {
public void filter_by_components_on_non_contextualized_search() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto(), "project");
ComponentDto file1 = newFileDto(project, null, "file1");
ComponentDto module = ComponentTesting.newModuleDto(project).setUuid("module");
ComponentDto module = newModuleDto(project).setUuid("module");
ComponentDto file2 = newFileDto(module, null, "file2");
ComponentDto subModule = ComponentTesting.newModuleDto(module).setUuid("subModule");
ComponentDto subModule = newModuleDto(module).setUuid("subModule");
ComponentDto file3 = newFileDto(subModule, null, "file3");
String view = "ABCD";
indexView(view, asList(project.uuid()));
@@ -318,39 +319,90 @@ public class IssueIndexTest {
Date now = new Date();
OrganizationDto organizationDto = newOrganizationDto();
ComponentDto project1 = newPrivateProjectDto(organizationDto);
IssueDoc project1Issue1 = newDoc().setProjectUuid(project1.uuid()).setFuncCreationDate(addDays(now, -10));
IssueDoc project1Issue2 = newDoc().setProjectUuid(project1.uuid()).setFuncCreationDate(addDays(now, -20));
IssueDoc project1Issue1 = newDoc(project1).setFuncCreationDate(addDays(now, -10));
IssueDoc project1Issue2 = newDoc(project1).setFuncCreationDate(addDays(now, -20));
ComponentDto project2 = newPrivateProjectDto(organizationDto);
IssueDoc project2Issue1 = newDoc().setProjectUuid(project2.uuid()).setFuncCreationDate(addDays(now, -15));
IssueDoc project2Issue2 = newDoc().setProjectUuid(project2.uuid()).setFuncCreationDate(addDays(now, -30));
IssueDoc project2Issue1 = newDoc(project2).setFuncCreationDate(addDays(now, -15));
IssueDoc project2Issue2 = newDoc(project2).setFuncCreationDate(addDays(now, -30));
indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);

// Search for issues of project 1 having less than 15 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), addDays(now, -15))),
.createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), addDays(now, -15))),
project1Issue1.key());

// Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
project1.uuid(), addDays(now, -14),
project2.uuid(), addDays(now, -25)
)),
project2.uuid(), addDays(now, -25))),
project1Issue1.key(), project2Issue1.key());

// Search for issues of project 1 having less than 30 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
project1.uuid(), addDays(now, -30)
)),
.createdAfterByProjectUuids(ImmutableMap.of(
project1.uuid(), addDays(now, -30))),
project1Issue1.key(), project1Issue2.key());

// Search for issues of project 1 and project 2 having less than 5 days
assertThatSearchReturnsOnly(IssueQuery.builder()
.createdAfterByProjectUuids(ImmutableMap.of(
project1.uuid(), addDays(now, -5),
project2.uuid(), addDays(now, -5)
)));
.createdAfterByProjectUuids(ImmutableMap.of(
project1.uuid(), addDays(now, -5),
project2.uuid(), addDays(now, -5))));
}

@Test
public void filter_one_issue_by_project_and_branch() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto anotherbBranch = db.components().insertProjectBranch(project);

IssueDoc issueOnProject = newDoc(project);
IssueDoc issueOnBranch = newDoc(branch);
IssueDoc issueOnAnotherBranch = newDoc(anotherbBranch);
indexIssues(issueOnProject, issueOnBranch, issueOnAnotherBranch);

assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()), issueOnBranch.key());
assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(branch.uuid())).branchUuid(branch.uuid()), issueOnBranch.key());
assertThatSearchReturnsOnly(IssueQuery.builder().projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()), issueOnBranch.key());
assertThatSearchReturnsOnly(IssueQuery.builder().componentUuids(singletonList(branch.uuid())).projectUuids(singletonList(project.uuid())).branchUuid(branch.uuid()),
issueOnBranch.key());
assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
}

@Test
public void issues_from_branch_component_children() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto projectModule = db.components().insertComponent(newModuleDto(project));
ComponentDto projectFile = db.components().insertComponent(newFileDto(projectModule));
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
ComponentDto branchModule = db.components().insertComponent(newModuleDto(branch));
ComponentDto branchFile = db.components().insertComponent(newFileDto(branchModule));

indexIssues(
newDoc("I1", project),
newDoc("I2", projectFile),
newDoc("I3", projectModule),
newDoc("I4", branch),
newDoc("I5", branchModule),
newDoc("I6", branchFile));

assertThatSearchReturnsOnly(IssueQuery.builder().branchUuid(branch.uuid()), "I4", "I5", "I6");
assertThatSearchReturnsOnly(IssueQuery.builder().moduleUuids(singletonList(branchModule.uuid())).branchUuid(branch.uuid()), "I5", "I6");
assertThatSearchReturnsOnly(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).branchUuid(branch.uuid()), "I6");
assertThatSearchReturnsEmpty(IssueQuery.builder().fileUuids(singletonList(branchFile.uuid())).branchUuid("unknown"));
}

@Test
public void branch_issues_are_ignored_when_no_branch_param() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));

IssueDoc projectIssue = newDoc(project);
IssueDoc branchIssue = newDoc(branch);
indexIssues(projectIssue, branchIssue);

assertThatSearchReturnsOnly(IssueQuery.builder(), projectIssue.key());
}

@Test

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java View File

@@ -462,7 +462,7 @@ public class IssueIndexerTest {
assertThat(doc.getId()).isEqualTo(issue.getKey());
assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
assertThat(doc.componentUuid()).isEqualTo(file.uuid());
assertThat(doc.projectUuid()).isEqualTo(project.uuid());
assertThat(doc.projectUuid()).isEqualTo(branch.getMainBranchProjectUuid());
assertThat(doc.branchUuid()).isEqualTo(branch.uuid());
assertThat(doc.isMainBranch()).isFalse();
}

+ 89
- 18
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java View File

@@ -31,12 +31,14 @@ import org.sonar.api.rule.RuleKey;
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.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.ActionFinder;
import org.sonar.server.issue.IssueFieldsSetter;
@@ -56,10 +58,13 @@ import org.sonar.server.view.index.ViewIndexer;
import org.sonar.server.ws.WsActionTester;
import org.sonar.server.ws.WsResponseCommonFormat;
import org.sonarqube.ws.Issues.Component;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Issues.Issue;
import org.sonarqube.ws.Issues.SearchWsResponse;
import org.sonarqube.ws.client.issue.IssuesWsParameters;

import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.emptySet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.utils.DateUtils.addDays;
@@ -72,6 +77,8 @@ import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
import static org.sonar.db.component.ComponentTesting.newSubView;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.db.issue.IssueTesting.newIssue;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
@@ -100,6 +107,21 @@ public class SearchActionComponentsTest {

private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat));

@Test
public void search_all_issues_when_no_parameter() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPublicProject();
ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
IssueDto issue = db.issues().insertIssue(newIssue(rule, project, projectFile));
allowAnyoneOnProjects(project);
indexIssues();

SearchWsResponse result = ws.newRequest().executeProtobuf(SearchWsResponse.class);

assertThat(result.getIssuesList()).extracting(Issues.Issue::getKey)
.containsExactlyInAnyOrder(issue.getKey());
}

@Test
public void issues_on_different_projects() throws Exception {
RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1")));
@@ -525,8 +547,9 @@ public class SearchActionComponentsTest {
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
db.components().insertComponents(newProjectCopy("PC1", project1, application));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
IssueDto issue1 = db.issues().insertIssue(i -> i.setProject(project1));
IssueDto issue2 = db.issues().insertIssue(i -> i.setProject(project2));
RuleDefinitionDto rule = db.rules().insert();
IssueDto issue1 = db.issues().insert(rule, project1, project1);
IssueDto issue2 = db.issues().insert(rule, project2, project2);
allowAnyoneOnProjects(project1, project2, application);
indexIssuesAndViews();

@@ -543,7 +566,8 @@ public class SearchActionComponentsTest {
ComponentDto project = db.components().insertPublicProject();
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
db.components().insertComponents(newProjectCopy("PC1", project, application));
db.issues().insertIssue(i -> i.setProject(project));
RuleDefinitionDto rule = db.rules().insert();
db.issues().insert(rule, project, project);
allowAnyoneOnProjects(project);
indexIssuesAndViews();

@@ -558,7 +582,8 @@ public class SearchActionComponentsTest {
public void search_application_without_projects() {
ComponentDto project = db.components().insertPublicProject();
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
db.issues().insertIssue(i -> i.setProject(project));
RuleDefinitionDto rule = db.rules().insert();
db.issues().insert(rule, project, project);
allowAnyoneOnProjects(project, application);
indexIssuesAndViews();

@@ -572,19 +597,20 @@ public class SearchActionComponentsTest {
@Test
public void search_by_application_and_by_leak() throws Exception {
Date now = new Date();
RuleDefinitionDto rule = db.rules().insert();
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
// Project 1
ComponentDto project1 = db.components().insertPublicProject();
db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
db.components().insertComponents(newProjectCopy("PC1", project1, application));
IssueDto project1Issue1 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -20)));
IssueDto project1Issue1 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -20)));
// Project 2
ComponentDto project2 = db.components().insertPublicProject();
db.components().insertSnapshot(project2, s -> s.setPeriodDate(addDays(now, -25).getTime()));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
IssueDto project2Issue1 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -30)));
IssueDto project2Issue1 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -30)));
// Permissions and index
allowAnyoneOnProjects(project1, project2, application);
indexIssuesAndViews();
@@ -606,8 +632,9 @@ public class SearchActionComponentsTest {
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
db.components().insertComponents(newProjectCopy("PC1", project1, application));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
IssueDto issue1 = db.issues().insertIssue(i -> i.setProject(project1));
IssueDto issue2 = db.issues().insertIssue(i -> i.setProject(project2));
RuleDefinitionDto rule = db.rules().insert();
IssueDto issue1 = db.issues().insert(rule, project1, project1);
IssueDto issue2 = db.issues().insert(rule, project2, project2);
allowAnyoneOnProjects(project1, project2, application);
indexIssuesAndViews();

@@ -624,19 +651,20 @@ public class SearchActionComponentsTest {
@Test
public void search_by_application_and_project_and_leak() throws Exception {
Date now = new Date();
RuleDefinitionDto rule = db.rules().insert();
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
// Project 1
ComponentDto project1 = db.components().insertPublicProject();
db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
db.components().insertComponents(newProjectCopy("PC1", project1, application));
IssueDto project1Issue1 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -20)));
IssueDto project1Issue1 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -20)));
// Project 2
ComponentDto project2 = db.components().insertPublicProject();
db.components().insertSnapshot(project2, s -> s.setPeriodDate(addDays(now, -25).getTime()));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
IssueDto project2Issue1 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -30)));
IssueDto project2Issue1 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -30)));
// Permissions and index
allowAnyoneOnProjects(project1, project2, application);
indexIssuesAndViews();
@@ -655,19 +683,20 @@ public class SearchActionComponentsTest {
@Test
public void search_by_application_and_by_leak_when_one_project_has_no_leak() throws Exception {
Date now = new Date();
RuleDefinitionDto rule = db.rules().insert();
ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
// Project 1
ComponentDto project1 = db.components().insertPublicProject();
db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime()));
db.components().insertComponents(newProjectCopy("PC1", project1, application));
IssueDto project1Issue1 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insertIssue(i -> i.setProject(project1).setIssueCreationDate(addDays(now, -20)));
IssueDto project1Issue1 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -10)));
IssueDto project1Issue2 = db.issues().insert(rule, project1, project1, i -> i.setIssueCreationDate(addDays(now, -20)));
// Project 2, without leak => no issue form it should be returned
ComponentDto project2 = db.components().insertPublicProject();
db.components().insertSnapshot(project2, s -> s.setPeriodDate(null));
db.components().insertComponents(newProjectCopy("PC2", project2, application));
IssueDto project2Issue1 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insertIssue(i -> i.setProject(project2).setIssueCreationDate(addDays(now, -30)));
IssueDto project2Issue1 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -15)));
IssueDto project2Issue2 = db.issues().insert(rule, project2, project2, i -> i.setIssueCreationDate(addDays(now, -30)));
// Permissions and index
allowAnyoneOnProjects(project1, project2, application);
indexIssuesAndViews();
@@ -682,6 +711,48 @@ public class SearchActionComponentsTest {
.doesNotContain(project1Issue2.getKey(), project2Issue1.getKey(), project2Issue2.getKey());
}

@Test
public void search_by_branch() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
userSession.addProjectPermission(UserRole.USER, project);
ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto branchFile = db.components().insertComponent(newFileDto(branch));
IssueDto branchIssue = db.issues().insertIssue(newIssue(rule, branch, branchFile));
allowAnyoneOnProjects(project);
indexIssuesAndViews();

SearchWsResponse result = ws.newRequest()
.setParam(PARAM_COMPONENT_KEYS, branch.getKey())
.setParam(PARAM_BRANCH, branch.getBranch())
.executeProtobuf(SearchWsResponse.class);

assertThat(result.getIssuesList()).extracting(Issues.Issue::getKey)
.containsExactlyInAnyOrder(branchIssue.getKey())
.doesNotContain(projectIssue.getKey());
}

@Test
public void does_not_return_branch_issues_on_not_contextualized_search() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
ComponentDto branch = db.components().insertProjectBranch(project);
ComponentDto branchFile = db.components().insertComponent(newFileDto(branch));
IssueDto branchIssue = db.issues().insertIssue(newIssue(rule, branch, branchFile));
allowAnyoneOnProjects(project);
indexIssuesAndViews();

SearchWsResponse result = ws.newRequest().executeProtobuf(SearchWsResponse.class);

assertThat(result.getIssuesList()).extracting(Issues.Issue::getKey)
.containsExactlyInAnyOrder(projectIssue.getKey())
.doesNotContain(branchIssue.getKey());
}

private void allowAnyoneOnProjects(ComponentDto... projects) {
userSession.registerComponents(projects);
Arrays.stream(projects).forEach(p -> permissionIndexer.allowOnlyAnyone(p));

+ 10
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java View File

@@ -70,6 +70,7 @@ import org.sonar.server.ws.WsResponseCommonFormat;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS;
@@ -128,13 +129,20 @@ public class SearchActionTest {
assertThat(def.responseExampleAsString()).isNotEmpty();

assertThat(def.params()).extracting("key").containsExactlyInAnyOrder(
"additionalFields", "asc", "assigned", "assignees", "authors", "componentKeys", "componentRootUuids", "componentRoots", "componentUuids", "components",
"additionalFields", "asc", "assigned", "assignees", "authors", "componentKeys", "componentRootUuids", "componentRoots", "componentUuids", "components", "branch",
"organization",
"createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "languages", "moduleUuids", "onComponentOnly",
"organization", "p", "projectUuids", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod",
"p", "projectUuids", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod",
"statuses", "tags", "types");

assertThat(def.param("organization"))
.matches(WebService.Param::isInternal)
.matches(p -> p.since().equals("6.4"));

WebService.Param branch = def.param(PARAM_BRANCH);
assertThat(branch.isInternal()).isTrue();
assertThat(branch.isRequired()).isFalse();
assertThat(branch.since()).isEqualTo("6.6");
}

@Test

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

@@ -69,6 +69,7 @@ public class IssuesWsParameters {
public static final String PARAM_DIRECTORIES = "directories";
public static final String PARAM_FILE_UUIDS = "fileUuids";
public static final String PARAM_ON_COMPONENT_ONLY = "onComponentOnly";
public static final String PARAM_BRANCH = "branch";
public static final String PARAM_ORGANIZATION = "organization";
public static final String PARAM_RULES = "rules";
public static final String PARAM_ACTIONS = "actions";

+ 11
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java View File

@@ -47,6 +47,7 @@ public class SearchWsRequest {
private List<String> languages;
private List<String> moduleUuids;
private Boolean onComponentOnly;
private String branch;
private String organization;
private Integer page;
private Integer pageSize;
@@ -442,4 +443,14 @@ public class SearchWsRequest {
this.projects = projects;
return this;
}

@CheckForNull
public String getBranch() {
return branch;
}

public SearchWsRequest setBranch(@Nullable String branch) {
this.branch = branch;
return this;
}
}

Loading…
Cancel
Save