From: Teryk Bellahsene Date: Wed, 18 May 2016 13:42:14 +0000 (+0200) Subject: SONAR-7578 Filter issues by leak period and by file, directory or module X-Git-Tag: 5.6-RC1~17 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=788fa6c2eeca7df4ba653c12e7e33224aff9a51f;p=sonarqube.git SONAR-7578 Filter issues by leak period and by file, directory or module --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java index 4846bb03af2..fa98d0fc3f2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueQueryService.java @@ -21,20 +21,24 @@ package org.sonar.server.issue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; +import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import java.util.Arrays; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.ObjectUtils; @@ -60,6 +64,8 @@ import org.sonarqube.ws.client.issue.IssueFilterParameters; import org.sonarqube.ws.client.issue.SearchWsRequest; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Predicates.notNull; +import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Lists.newArrayList; import static java.lang.String.format; import static org.sonar.api.utils.DateUtils.longToDate; @@ -68,6 +74,10 @@ import static org.sonar.db.component.ComponentDtoFunctions.toProjectUuid; import static org.sonar.db.component.ComponentDtoFunctions.toUuid; import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonar.server.ws.WsUtils.checkRequest; +import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENTS; +import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_KEYS; +import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_ROOTS; +import static org.sonarqube.ws.client.issue.IssueFilterParameters.COMPONENT_UUIDS; import static org.sonarqube.ws.client.issue.IssueFilterParameters.CREATED_AFTER; import static org.sonarqube.ws.client.issue.IssueFilterParameters.CREATED_IN_LAST; import static org.sonarqube.ws.client.issue.IssueFilterParameters.SINCE_LEAK_PERIOD; @@ -206,7 +216,7 @@ public class IssueQueryService { request.getFileUuids(), request.getAuthors()); - builder.createdAfter(buildCreatedAfterFromRequest(session, request, allComponentUuids, effectiveOnComponentOnly)); + builder.createdAfter(buildCreatedAfterFromRequest(session, request, allComponentUuids)); String sort = request.getSort(); if (!Strings.isNullOrEmpty(sort)) { @@ -220,7 +230,7 @@ public class IssueQueryService { } } - private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set componentUuids, boolean effectiveOnComponentOnly) { + private Date buildCreatedAfterFromRequest(DbSession dbSession, SearchWsRequest request, Set componentUuids) { Date createdAfter = parseAsDateTime(request.getCreatedAfter()); String createdInLast = request.getCreatedInLast(); @@ -229,29 +239,15 @@ public class IssueQueryService { } checkRequest(createdAfter == null, "'%s' and '%s' cannot be set simultaneously", CREATED_AFTER, SINCE_LEAK_PERIOD); - Set allComponentUuids = new HashSet<>(componentUuids); - if (!effectiveOnComponentOnly) { - if (request.getProjectKeys() != null) { - allComponentUuids.addAll(componentUuids(dbSession, request.getProjectKeys())); - } - if (request.getProjectUuids() != null) { - allComponentUuids.addAll(request.getProjectUuids()); - } - if (request.getModuleUuids() != null) { - allComponentUuids.addAll(request.getModuleUuids()); - } - if (request.getFileUuids() != null) { - allComponentUuids.addAll(request.getFileUuids()); - } - } - checkArgument(allComponentUuids.size() == 1, "One and only one component must be provided when searching since leak period"); - String uuid = allComponentUuids.iterator().next(); + checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period"); + String uuid = componentUuids.iterator().next(); // TODO use ComponentFinder instead Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, uuid); return buildCreatedAfterFromDates(createdAfterFromSnapshot, createdInLast); } + @CheckForNull private Date findCreatedAfterFromComponentUuid(DbSession dbSession, String uuid) { ComponentDto component = checkFoundWithOptional(componentService.getByUuid(uuid), "Component with id '%s' not found", uuid); SnapshotDto snapshot = dbClient.snapshotDao().selectLastSnapshotByComponentId(dbSession, component.getId()); @@ -285,9 +281,9 @@ public class IssueQueryService { Set allComponentUuids) { boolean effectiveOnComponentOnly = false; - failIfBothParametersSet(componentRootUuids, componentRoots, "componentRoots and componentRootUuids cannot be set simultaneously"); - failIfBothParametersSet(componentUuids, components, "components and componentUuids cannot be set simultaneously"); - failIfBothParametersSet(componentKeys, componentUuids, "componentKeys and componentUuids cannot be set simultaneously"); + checkArgument(atMostOneNonNullElement(components, componentUuids, componentKeys, componentRootUuids, componentRoots), + "At most one of the following parameters can be provided: %s, %s, %s, %s, %s", + COMPONENT_KEYS, COMPONENT_UUIDS, COMPONENTS, COMPONENT_ROOTS, COMPONENT_UUIDS); if (componentRootUuids != null) { allComponentUuids.addAll(componentRootUuids); @@ -308,10 +304,10 @@ public class IssueQueryService { return effectiveOnComponentOnly; } - private void failIfBothParametersSet(@Nullable Collection uuids, @Nullable Collection keys, String message) { - if (uuids != null && keys != null) { - throw new IllegalArgumentException(message); - } + private static boolean atMostOneNonNullElement(Object... objects) { + return !from(Arrays.asList(objects)) + .filter(notNull()) + .anyMatch(new HasTwoOrMoreElements()); } private void addComponentParameters(IssueQuery.Builder builder, DbSession session, @@ -330,7 +326,7 @@ public class IssueQueryService { } builder.authors(authors); - failIfBothParametersSet(projectUuids, projects, "projects and projectUuids cannot be set simultaneously"); + checkArgument(projectUuids == null || projects == null, "projects and projectUuids cannot be set simultaneously"); if (projectUuids != null) { builder.projectUuids(projectUuids); } else { @@ -477,4 +473,18 @@ public class IssueQueryService { } } } + + private static class HasTwoOrMoreElements implements Predicate { + private AtomicInteger counter; + + private HasTwoOrMoreElements() { + this.counter = new AtomicInteger(); + } + + @Override + public boolean apply(@Nonnull Object input) { + Objects.requireNonNull(input); + return counter.incrementAndGet() >= 2; + } + } } 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 94796e7e8b9..f8f4602bf37 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 @@ -21,7 +21,6 @@ package org.sonar.server.issue.ws; import com.google.common.base.Function; import com.google.common.collect.Lists; -import com.google.common.io.Resources; import java.util.Collection; import java.util.EnumSet; import java.util.LinkedHashMap; @@ -122,12 +121,14 @@ public class SearchAction implements IssuesWsAction { .createAction(SEARCH_ACTION) .setHandler(this) .setDescription( - "Search for issues. Requires Browse permission on project(s).
" + - "Since 5.5, response field 'debt' has been renamed to 'effort'.
" + - "Since 5.5, response field 'actionPlan' has been removed.
" + - "Since 5.5, response field 'reporter' has been removed, as manual issue feature has been dropped") + "Search for issues. Requires Browse permission on project(s).
" + + "At most one of the following parameters can be provided at the same time: %s, %s, %s, %s, %s
" + + "Since 5.5, response field 'debt' has been renamed to 'effort'.
" + + "Since 5.5, response field 'actionPlan' has been removed.
" + + "Since 5.5, response field 'reporter' has been removed, as manual issue feature has been dropped.", + COMPONENT_KEYS, COMPONENT_UUIDS, COMPONENTS, COMPONENT_ROOT_UUIDS, COMPONENT_ROOTS) .setSince("3.6") - .setResponseExample(Resources.getResource(this.getClass(), "example-search.json")); + .setResponseExample(getClass().getResource("example-search.json")); action.addPagingParams(100, MAX_LIMIT); action.createParam(Param.FACETS) @@ -231,8 +232,7 @@ public class SearchAction implements IssuesWsAction { action.createParam(COMPONENT_KEYS) .setDescription("To retrieve issues associated to a specific list of components sub-components (comma-separated list of component keys). " + - "A component can be a view, developer, project, module, directory or file. " + - "If this parameter is set, componentUuids must not be set.") + "A component can be a view, developer, project, module, directory or file.") .setExampleValue(KEY_PROJECT_EXAMPLE_001); action.createParam(COMPONENTS) .setDeprecatedSince("5.1") @@ -240,10 +240,8 @@ public class SearchAction implements IssuesWsAction { action.createParam(COMPONENT_UUIDS) .setDescription("To retrieve issues associated to a specific list of components their sub-components (comma-separated list of component UUIDs). " + INTERNAL_PARAMETER_DISCLAIMER + - "A component can be a project, module, directory or file. " + - "If this parameter is set, componentKeys must not be set.") + "A component can be a project, module, directory or file.") .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2"); - action.createParam(COMPONENT_ROOTS) .setDeprecatedSince("5.1") .setDescription("If used, will have the same meaning as componentKeys AND onComponentOnly=false."); @@ -273,9 +271,10 @@ public class SearchAction implements IssuesWsAction { .setExampleValue("7d8749e8-3070-4903-9188-bdd82933bb92"); action.createParam(DIRECTORIES) - .setDescription("Since 5.1. To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " + + .setDescription("To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " + "This parameter is only meaningful when a module is selected. " + INTERNAL_PARAMETER_DISCLAIMER) + .setSince("5.1") .setExampleValue("src/main/java/org/sonar/server/"); action.createParam(FILE_UUIDS) @@ -362,12 +361,6 @@ public class SearchAction implements IssuesWsAction { addMandatoryValuesToFacet(facets, LANGUAGES, request.getLanguages()); addMandatoryValuesToFacet(facets, TAGS, request.getTags()); addMandatoryValuesToFacet(facets, TYPES, RuleType.names()); - List actionPlans = Lists.newArrayList(""); - List actionPlansFromRequest = request.getActionPlans(); - if (actionPlansFromRequest != null) { - actionPlans.addAll(actionPlansFromRequest); - } - addMandatoryValuesToFacet(facets, DEPRECATED_ACTION_PLANS, actionPlans); addMandatoryValuesToFacet(facets, COMPONENT_UUIDS, request.getComponentUuids()); for (String facetName : request.getFacets()) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java index 45e2a609ed7..a3fc80c56b2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueQueryServiceTest.java @@ -24,7 +24,6 @@ import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.Map; import org.assertj.core.api.Fail; @@ -194,12 +193,10 @@ public class IssueQueryServiceTest { map.put("components", componentKeys); map.put("componentUuids", newArrayList("ABCD")); - try { - underTest.createFromMap(map); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("components and componentUuids cannot be set simultaneously"); - } + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids"); + + underTest.createFromMap(map); } @Test @@ -222,12 +219,10 @@ public class IssueQueryServiceTest { map.put("componentRoots", newArrayList("org.apache")); map.put("componentRootUuids", newArrayList("ABCD")); - try { - underTest.createFromMap(map); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("componentRoots and componentRootUuids cannot be set simultaneously"); - } + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("At most one of the following parameters can be provided: componentKeys, componentUuids, components, componentRoots, componentUuids"); + + underTest.createFromMap(map); } @Test @@ -472,8 +467,7 @@ public class IssueQueryServiceTest { underTest.createFromRequest(new SearchWsRequest() .setSinceLeakPeriod(true) - .setComponentUuids(Collections.singletonList("component-uuid")) - .setProjectUuids(Collections.singletonList("project-uuid"))); + .setComponentUuids(newArrayList("component-uuid", "project-uuid"))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java index 23f995b45cd..85c181cc30c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java @@ -240,6 +240,7 @@ public class SearchActionComponentsMediumTest { tester.get(IssueIndexer.class).indexAll(); wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION) + .setParam(IssueFilterParameters.COMPONENT_UUIDS, project.uuid()) .setParam(IssueFilterParameters.FILE_UUIDS, file.uuid()) .setParam(IssueFilterParameters.SINCE_LEAK_PERIOD, "true") .execute() diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index e42473d0337..0e1eba6c763 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -264,8 +264,19 @@ public interface WebService extends Definable { return this; } - public NewAction setDescription(@Nullable String s) { - this.description = s; + /** + * Used in Orchestrator + */ + public NewAction setDescription(@Nullable String description) { + this.description = description; + return this; + } + + /** + * @since 5.6 + */ + public NewAction setDescription(@Nullable String description, Object... descriptionArgument) { + this.description = description == null ? null : String.format(description, descriptionArgument); return this; } @@ -274,6 +285,9 @@ public interface WebService extends Definable { return this; } + /** + * @since 5.3 + */ public NewAction setDeprecatedSince(@Nullable String deprecatedSince) { this.deprecatedSince = deprecatedSince; return this; @@ -592,11 +606,17 @@ public interface WebService extends Definable { this.key = key; } + /** + * @since 5.3 + */ public NewParam setSince(@Nullable String since) { this.since = since; return this; } + /** + * @since 5.3 + */ public NewParam setDeprecatedSince(@Nullable String deprecatedSince) { this.deprecatedSince = deprecatedSince; return this; @@ -610,8 +630,16 @@ public interface WebService extends Definable { return this; } - public NewParam setDescription(@Nullable String s) { - this.description = s; + public NewParam setDescription(@Nullable String description) { + this.description = description; + return this; + } + + /** + * @since 5.6 + */ + public NewParam setDescription(@Nullable String description, Object... descriptionArgument) { + this.description = description == null ? null : String.format(description, descriptionArgument); return this; }