diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-08-10 13:36:54 +0200 |
---|---|---|
committer | Janos Gyerik <janos.gyerik@sonarsource.com> | 2017-09-12 10:59:55 +0200 |
commit | 47ef3bd0dc6610d0034f1753e06e8ac467eb906b (patch) | |
tree | 5778e6442902616b7a0ea1138795815de17c6eea | |
parent | 16a7a9b8996218475966392fcf6c00e9150d70ef (diff) | |
download | sonarqube-47ef3bd0dc6610d0034f1753e06e8ac467eb906b.tar.gz sonarqube-47ef3bd0dc6610d0034f1753e06e8ac467eb906b.zip |
SONAR-9616 Support branch in api/issues/search
19 files changed, 452 insertions, 146 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index f12e59a954c..b6456c4e12d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -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) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java index 692948c085c..0f3f5d74c33 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -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); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index 0409bbd1f62..d7079ac7a10 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -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"/> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java index e3f4e89f355..21eae5e1253 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java @@ -250,6 +250,24 @@ public class ComponentDaoTest { } @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(); ComponentDto project2 = db.components().insertPrivateProject(); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java index 9723e0d689f..a3a23ec35e1 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDbTester.java @@ -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) diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java index 4805a61b58f..35538b9fc77 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQuery.java @@ -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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java index e4184a39369..71e53f29e5c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryFactory.java @@ -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 diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java index b975c525d9d..5099362e291 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 098ef1720f3..de9e1ae8425 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -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)) diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java index a6cb622d013..4866510c08d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java @@ -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() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java index 2dcc8de454f..809646fdfe5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueDocTesting.java @@ -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); - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java index 7b02648e8b9..01ad51d4888 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryFactoryTest.java @@ -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")) @@ -134,6 +137,23 @@ public class IssueQueryFactoryTest { } @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(); assertThat(IssueQueryFactory.toRules("")).isEmpty(); @@ -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())); @@ -330,6 +349,65 @@ public class IssueQueryFactoryTest { } @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() .setCreatedAfter("2013-07-25T07:35:00+0100") @@ -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 diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java index a822662a268..480a72f01c4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryTest.java @@ -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(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java index b407a17cee9..c68a2aecfc5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java @@ -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 diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java index 5d9e3245848..4379bbb3480 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java @@ -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(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java index 3f004b84e48..1199319529a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java @@ -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; @@ -101,6 +108,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"))); OrganizationDto organization1 = db.organizations().insert(); @@ -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)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index f264318b94b..850c3099fb3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -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 diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java index 20fe0eb7ff5..7dd45528e06 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java @@ -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"; diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java index a0011cffe92..427ea7f5845 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/SearchWsRequest.java @@ -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; + } } |