]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13597 Add new 'Scope' facet to list of issue facets
authorMichal Duda <michal.duda@sonarsource.com>
Fri, 10 Jul 2020 13:13:44 +0000 (15:13 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 3 Sep 2020 20:07:20 +0000 (20:07 +0000)
21 files changed:
server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueScope.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope_2.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_test_scope.json [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
sonar-ws/src/main/protobuf/ws-issues.proto

index cd3b8174d90cf3ed7b23e8388bb9910ba3877c45..587b8fae9e169e8941e63de872eadc2d4e301648 100644 (file)
@@ -44,6 +44,7 @@ public class SearchRequest {
   private List<String> facets;
   private List<String> fileUuids;
   private List<String> issues;
+  private Set<String> scopes;
   private List<String> languages;
   private List<String> moduleUuids;
   private Boolean onComponentOnly;
@@ -67,6 +68,10 @@ public class SearchRequest {
   private List<String> sonarsourceSecurity;
   private List<String> cwe;
 
+  public SearchRequest() {
+    // nothing to do here
+  }
+
   @CheckForNull
   public List<String> getActionPlans() {
     return actionPlans;
@@ -227,6 +232,16 @@ public class SearchRequest {
     return this;
   }
 
+  @CheckForNull
+  public Set<String> getScopes() {
+    return scopes;
+  }
+
+  public SearchRequest setScopes(@Nullable Collection<String> scopes) {
+    this.scopes = scopes == null ? null : ImmutableSet.copyOf(scopes);
+    return this;
+  }
+
   @CheckForNull
   public List<String> getLanguages() {
     return languages;
index c5854408c1d0c7870fb52caacbcc0333062b9ccf..f08ca6222f28316de852a717e91d99f18be3d9c1 100644 (file)
@@ -83,6 +83,10 @@ public class IssueDoc extends BaseDoc {
     return getField(IssueIndexDefinition.FIELD_ISSUE_RULE_UUID);
   }
 
+  public IssueScope scope() {
+    return IssueScope.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_SCOPE));
+  }
+
   public String language() {
     return getField(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE);
   }
@@ -195,6 +199,11 @@ public class IssueDoc extends BaseDoc {
     return this;
   }
 
+  public IssueDoc setScope(IssueScope s) {
+    setField(IssueIndexDefinition.FIELD_ISSUE_SCOPE, s.toString());
+    return this;
+  }
+
   public IssueDoc setLanguage(@Nullable String s) {
     setField(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, s);
     return this;
index fb9e19da2f0e1429c213854f91f9ad3e025cfa03..d73bc110179855ab783efedfdaf3f4bc4b773e61 100644 (file)
@@ -57,6 +57,7 @@ public class IssueIndexDefinition implements IndexDefinition {
    */
   public static final String FIELD_ISSUE_FUNC_CLOSED_AT = "issueClosedAt";
   public static final String FIELD_ISSUE_KEY = "key";
+  public static final String FIELD_ISSUE_SCOPE = "scope";
   public static final String FIELD_ISSUE_LANGUAGE = "language";
   public static final String FIELD_ISSUE_LINE = "line";
   public static final String FIELD_ISSUE_MODULE_UUID = "module";
@@ -142,6 +143,7 @@ public class IssueIndexDefinition implements IndexDefinition {
     mapping.createDateTimeField(FIELD_ISSUE_FUNC_UPDATED_AT);
     mapping.createDateTimeField(FIELD_ISSUE_FUNC_CLOSED_AT);
     mapping.keywordFieldBuilder(FIELD_ISSUE_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+    mapping.keywordFieldBuilder(FIELD_ISSUE_SCOPE).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_ISSUE_LANGUAGE).disableNorms().build();
     mapping.createIntegerField(FIELD_ISSUE_LINE);
     mapping.keywordFieldBuilder(FIELD_ISSUE_MODULE_UUID).disableNorms().build();
index d77b094947c8df2c0f90226d6f440a953c080bb4..d4749a46d89931d7f78294a6b863fe297d62c6c4 100644 (file)
@@ -32,6 +32,7 @@ import java.util.stream.IntStream;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.rules.RuleType;
 import org.sonar.db.DatabaseUtils;
@@ -80,7 +81,8 @@ class IssueIteratorForSingleChunk implements IssueIterator {
     // column 21
     "i.tags",
     "i.issue_type",
-    "r.security_standards"
+    "r.security_standards",
+    "c.qualifier"
   };
 
   private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
@@ -235,6 +237,8 @@ class IssueIteratorForSingleChunk implements IssueIterator {
       doc.setSansTop25(securityStandards.getSansTop25());
       doc.setSonarSourceSecurityCategory(sqCategory);
       doc.setVulnerabilityProbability(sqCategory.getVulnerability());
+
+      doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(24)) ? IssueScope.TEST : IssueScope.MAIN);
       return doc;
     }
 
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueScope.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueScope.java
new file mode 100644 (file)
index 0000000..92e3121
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+/**
+ * @since 8.5
+ */
+public enum IssueScope {
+  MAIN, TEST
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java
new file mode 100644 (file)
index 0000000..8ce9da7
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue;
+
+import org.junit.Test;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SearchRequestTest {
+
+  @Test
+  public void settersAndGetters() {
+    SearchRequest underTest = new SearchRequest()
+      .setIssues(singletonList("anIssueKey"))
+      .setSeverities(asList("MAJOR", "MINOR"))
+      .setStatuses(singletonList("CLOSED"))
+      .setResolutions(singletonList("FALSE-POSITIVE"))
+      .setResolved(true)
+      .setProjects(singletonList("project-a"))
+      .setModuleUuids(singletonList("module-a"))
+      .setDirectories(singletonList("aDirPath"))
+      .setFileUuids(asList("file-a", "file-b"))
+      .setAssigneesUuid(asList("user-a", "user-b"))
+      .setScopes(asList("MAIN", "TEST"))
+      .setLanguages(singletonList("xoo"))
+      .setTags(asList("tag1", "tag2"))
+      .setOrganization("org-a")
+      .setAssigned(true)
+      .setCreatedAfter("2013-04-16T09:08:24+0200")
+      .setCreatedBefore("2013-04-17T09:08:24+0200")
+      .setRules(asList("key-a", "key-b"))
+      .setSort("CREATION_DATE")
+      .setAsc(true);
+
+    assertThat(underTest.getIssues()).containsOnlyOnce("anIssueKey");
+    assertThat(underTest.getSeverities()).containsExactly("MAJOR", "MINOR");
+    assertThat(underTest.getStatuses()).containsExactly("CLOSED");
+    assertThat(underTest.getResolutions()).containsExactly("FALSE-POSITIVE");
+    assertThat(underTest.getResolved()).isTrue();
+    assertThat(underTest.getProjects()).containsExactly("project-a");
+    assertThat(underTest.getModuleUuids()).containsExactly("module-a");
+    assertThat(underTest.getDirectories()).containsExactly("aDirPath");
+    assertThat(underTest.getFileUuids()).containsExactly("file-a", "file-b");
+    assertThat(underTest.getAssigneeUuids()).containsExactly("user-a", "user-b");
+    assertThat(underTest.getScopes()).containsExactly("MAIN", "TEST");
+    assertThat(underTest.getLanguages()).containsExactly("xoo");
+    assertThat(underTest.getTags()).containsExactly("tag1", "tag2");
+    assertThat(underTest.getOrganization()).isEqualTo("org-a");
+    assertThat(underTest.getAssigned()).isTrue();
+    assertThat(underTest.getCreatedAfter()).isEqualTo("2013-04-16T09:08:24+0200");
+    assertThat(underTest.getCreatedBefore()).isEqualTo("2013-04-17T09:08:24+0200");
+    assertThat(underTest.getRules()).containsExactly("key-a", "key-b");
+    assertThat(underTest.getSort()).isEqualTo("CREATION_DATE");
+    assertThat(underTest.getAsc()).isTrue();
+  }
+
+  @Test
+  public void setScopesAcceptsNull() {
+    SearchRequest underTest = new SearchRequest().setScopes(null);
+
+    assertThat(underTest.getScopes()).isNull();
+  }
+}
index 07da774c8105b7a63e859994c5b0b5593a56253f..326e8399ef838c68ee1da7bd3eaba54fc0429394 100644 (file)
@@ -136,6 +136,7 @@ public class IssueIndexerTest {
     assertThat(doc.creationDate()).isEqualToIgnoringMillis(issue.getIssueCreationDate());
     assertThat(doc.directoryPath()).isEqualTo(dir.path());
     assertThat(doc.filePath()).isEqualTo(file.path());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
     assertThat(doc.language()).isEqualTo(issue.getLanguage());
     assertThat(doc.line()).isEqualTo(issue.getLine());
     // functional date
@@ -507,10 +508,59 @@ public class IssueIndexerTest {
     assertThat(doc.projectUuid()).isEqualTo(branch.getMainBranchProjectUuid());
     assertThat(doc.branchUuid()).isEqualTo(branch.uuid());
     assertThat(doc.isMainBranch()).isFalse();
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
   }
 
   @Test
-  public void getType(){
+  public void issue_on_test_file_has_test_scope() {
+    RuleDefinitionDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1").setQualifier("UTS"));
+    IssueDto issue = db.issues().insert(rule, project, file);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
+    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.TEST);
+  }
+
+  @Test
+  public void issue_on_directory_has_main_code_scope() {
+    RuleDefinitionDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    IssueDto issue = db.issues().insert(rule, project, dir);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
+    assertThat(doc.componentUuid()).isEqualTo(dir.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+  }
+
+  @Test
+  public void issue_on_project_has_main_code_scope() {
+    RuleDefinitionDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    IssueDto issue = db.issues().insert(rule, project, project);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.organizationUuid()).isEqualTo(organization.getUuid());
+    assertThat(doc.componentUuid()).isEqualTo(project.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+  }
+
+  @Test
+  public void getType() {
     Assertions.assertThat(underTest.getType()).isEqualTo(StartupIndexer.Type.ASYNCHRONOUS);
   }
 
index fbe410e598f9b4301d08873808ad416fc45a4a24..16340cc2ea55e3be1a74ce8954c965fd043208aa 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.rules.RuleType;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.server.issue.index.IssueDoc;
+import org.sonar.server.issue.index.IssueScope;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.math.RandomUtils.nextInt;
@@ -60,6 +61,7 @@ public class IssueDocTesting {
     doc.setType(RuleType.CODE_SMELL);
     doc.setAssigneeUuid("assignee_uuid_" + randomAlphabetic(26));
     doc.setAuthorLogin("author_" + randomAlphabetic(5));
+    doc.setScope(IssueScope.MAIN);
     doc.setLanguage("language_" + randomAlphabetic(5));
     doc.setComponentUuid(Uuids.createFast());
     doc.setFilePath("filePath_" + randomAlphabetic(5));
index db39fc3028e56f180dbca15c3d856d46ac0c6b6c..943f35f8a6e08cfdbaa868712babbd481ff320ba 100644 (file)
@@ -127,6 +127,7 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.PROJECT_UUIDS;
 import static org.sonar.server.issue.index.IssueIndex.Facet.RESOLUTIONS;
 import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
 import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
+import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
 import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
 import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY;
 import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
@@ -155,6 +156,7 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJ
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RULE_UUID;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
 import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
@@ -183,6 +185,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_1
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
@@ -231,6 +234,7 @@ public class IssueIndex {
     // Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
     RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
     TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
+    SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
     LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
     RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE),
     TAGS(PARAM_TAGS, FIELD_ISSUE_TAGS, STICKY, MAX_FACET_SIZE),
@@ -416,6 +420,7 @@ public class IssueIndex {
     // Field Filters
     filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
     filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
+    filters.addFilter(FIELD_ISSUE_SCOPE, SCOPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SCOPE, query.scopes()));
     filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
     filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
     filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
@@ -662,6 +667,7 @@ public class IssueIndex {
     addFacetIfNeeded(options, aggregationHelper, esRequest, MODULE_UUIDS, query.moduleUuids().toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, DIRECTORIES, query.directories().toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, FILE_UUIDS, query.fileUuids().toArray());
+    addFacetIfNeeded(options, aggregationHelper, esRequest, SCOPES, query.scopes().toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, LANGUAGES, query.languages().toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, RULES, query.rules().stream().map(RuleDefinitionDto::getUuid).toArray());
     addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHORS, query.authors().toArray());
index 950d132b2ecf01eb8952f95f9dd1cadddd9fc119..ededee7f2acb9b2c3ca7ab5062fe7296421e9f24 100644 (file)
@@ -75,6 +75,7 @@ public class IssueQuery {
   private final Collection<RuleDefinitionDto> rules;
   private final Collection<String> assignees;
   private final Collection<String> authors;
+  private final Collection<String> scopes;
   private final Collection<String> languages;
   private final Collection<String> tags;
   private final Collection<String> types;
@@ -111,6 +112,7 @@ public class IssueQuery {
     this.rules = defaultCollection(builder.rules);
     this.assignees = defaultCollection(builder.assigneeUuids);
     this.authors = defaultCollection(builder.authors);
+    this.scopes = defaultCollection(builder.scopes);
     this.languages = defaultCollection(builder.languages);
     this.tags = defaultCollection(builder.tags);
     this.types = defaultCollection(builder.types);
@@ -189,6 +191,10 @@ public class IssueQuery {
     return authors;
   }
 
+  public Collection<String> scopes() {
+    return scopes;
+  }
+
   public Collection<String> languages() {
     return languages;
   }
@@ -303,6 +309,7 @@ public class IssueQuery {
     private Collection<RuleDefinitionDto> rules;
     private Collection<String> assigneeUuids;
     private Collection<String> authors;
+    private Collection<String> scopes;
     private Collection<String> languages;
     private Collection<String> tags;
     private Collection<String> types;
@@ -398,6 +405,11 @@ public class IssueQuery {
       return this;
     }
 
+    public Builder scopes(@Nullable Collection<String> s) {
+      this.scopes = s;
+      return this;
+    }
+
     public Builder languages(@Nullable Collection<String> l) {
       this.languages = l;
       return this;
index 1b5acb2ce62bd344eb24303e9cfceae4a0f591e9..5103a86d1be36b8c46b7fc71f7b6b2acf730a587 100644 (file)
@@ -117,6 +117,7 @@ public class IssueQueryFactory {
         .rules(ruleKeysToRuleId(dbSession, request.getRules()))
         .assigneeUuids(request.getAssigneeUuids())
         .authors(request.getAuthors())
+        .scopes(request.getScopes())
         .languages(request.getLanguages())
         .tags(request.getTags())
         .types(request.getTypes())
index 61d324734cdcf53dd2a5ca6df7c00762d1cf7921..72ed3d3c018a88525d039ea750a02607f0f2de18 100644 (file)
@@ -67,10 +67,9 @@ public class IssueQueryFactoryTest {
   @Rule
   public DbTester db = DbTester.create();
 
-  private RuleDbTester ruleDbTester = new RuleDbTester(db);
-
-  private Clock clock = mock(Clock.class);
-  private IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), clock, userSession);
+  private final RuleDbTester ruleDbTester = new RuleDbTester(db);
+  private final Clock clock = mock(Clock.class);
+  private final IssueQueryFactory underTest = new IssueQueryFactory(db.getDbClient(), clock, userSession);
 
   @Test
   public void create_from_parameters() {
@@ -94,6 +93,7 @@ public class IssueQueryFactoryTest {
       .setDirectories(asList("aDirPath"))
       .setFileUuids(asList(file.uuid()))
       .setAssigneesUuid(asList(user.getUuid()))
+      .setScopes(asList("MAIN", "TEST"))
       .setLanguages(asList("xoo"))
       .setTags(asList("tag1", "tag2"))
       .setOrganization(organization.getKey())
@@ -115,6 +115,7 @@ public class IssueQueryFactoryTest {
     assertThat(query.moduleUuids()).containsOnly(module.uuid());
     assertThat(query.fileUuids()).containsOnly(file.uuid());
     assertThat(query.assignees()).containsOnly(user.getUuid());
+    assertThat(query.scopes()).containsOnly("TEST", "MAIN");
     assertThat(query.languages()).containsOnly("xoo");
     assertThat(query.tags()).containsOnly("tag1", "tag2");
     assertThat(query.organizationUuid()).isEqualTo(organization.getUuid());
index 032af08365aaccc6ddad7a2184d1798b715f4de9..f2ab8cee099c2c533e3ef6a06bb376a5c3c544eb 100644 (file)
@@ -52,6 +52,7 @@ import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
 import org.sonar.server.issue.index.IssueQuery;
 import org.sonar.server.issue.index.IssueQueryFactory;
+import org.sonar.server.issue.index.IssueScope;
 import org.sonar.server.security.SecurityStandards.SQCategory;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Issues.SearchWsResponse;
@@ -121,6 +122,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLVED;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
@@ -131,6 +133,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
 public class SearchAction implements IssuesWsAction {
 
   private static final String LOGIN_MYSELF = "__me__";
+  private static final Set<String> ISSUE_SCOPES = Arrays.stream(IssueScope.values()).map(Enum::name).collect(Collectors.toSet());
   private static final EnumSet<RuleType> ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT));
 
   static final List<String> SUPPORTED_FACETS = ImmutableList.of(
@@ -146,6 +149,7 @@ public class SearchAction implements IssuesWsAction {
     DEPRECATED_PARAM_AUTHORS,
     PARAM_AUTHOR,
     PARAM_DIRECTORIES,
+    PARAM_SCOPES,
     PARAM_LANGUAGES,
     PARAM_TAGS,
     PARAM_TYPES,
@@ -185,7 +189,7 @@ public class SearchAction implements IssuesWsAction {
       .createAction(ACTION_SEARCH)
       .setHandler(this)
       .setDescription("Search for issues.<br>Requires the 'Browse' permission on the specified project(s)."
-          + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
+        + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
       .setSince("3.6")
       .setChangelog(
         new Change("8.4", "parameters 'componentUuids', 'projectKeys' has been dropped."),
@@ -284,6 +288,10 @@ public class SearchAction implements IssuesWsAction {
     action.createParam(PARAM_ASSIGNED)
       .setDescription("To retrieve assigned or unassigned issues")
       .setBooleanPossibleValues();
+    action.createParam(PARAM_SCOPES)
+      .setDescription("Comma-separated list of scopes. Available since 8.5")
+      .setPossibleValues(IssueScope.MAIN.name(), IssueScope.TEST.name())
+      .setExampleValue(format("%s,%s", IssueScope.MAIN.name(), IssueScope.TEST.name()));
     action.createParam(PARAM_LANGUAGES)
       .setDescription("Comma-separated list of languages. Available since 4.4")
       .setExampleValue("java,js");
@@ -450,6 +458,7 @@ public class SearchAction implements IssuesWsAction {
     addMandatoryValuesToFacet(facets, PARAM_ASSIGNEES, assignees);
     addMandatoryValuesToFacet(facets, FACET_ASSIGNED_TO_ME, singletonList(userSession.getUuid()));
     addMandatoryValuesToFacet(facets, PARAM_RULES, query.rules().stream().map(RuleDefinitionDto::getUuid).collect(toList()));
+    addMandatoryValuesToFacet(facets, PARAM_SCOPES, ISSUE_SCOPES);
     addMandatoryValuesToFacet(facets, PARAM_LANGUAGES, request.getLanguages());
     addMandatoryValuesToFacet(facets, PARAM_TAGS, request.getTags());
 
@@ -517,6 +526,7 @@ public class SearchAction implements IssuesWsAction {
       .setFacets(request.paramAsStrings(FACETS))
       .setFileUuids(request.paramAsStrings(PARAM_FILE_UUIDS))
       .setIssues(request.paramAsStrings(PARAM_ISSUES))
+      .setScopes(request.paramAsStrings(PARAM_SCOPES))
       .setLanguages(request.paramAsStrings(PARAM_LANGUAGES))
       .setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS))
       .setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY))
index 5092b8ee638cc8ee1e5c0b3a8dd2fef0f490d2a7..0b737cfcfd326e31d4830b645bae41220b7c621e 100644 (file)
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.Set;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.DateUtils;
@@ -43,6 +44,7 @@ import org.sonar.db.user.UserDto;
 import org.sonar.markdown.Markdown;
 import org.sonar.server.es.Facets;
 import org.sonar.server.issue.TextRangeResponseFormatter;
+import org.sonar.server.issue.index.IssueScope;
 import org.sonar.server.issue.workflow.Transition;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Common.Comment;
@@ -212,6 +214,7 @@ public class SearchResponseFormat {
     ofNullable(dto.getIssueCreationDate()).map(DateUtils::formatDateTime).ifPresent(issueBuilder::setCreationDate);
     ofNullable(dto.getIssueUpdateDate()).map(DateUtils::formatDateTime).ifPresent(issueBuilder::setUpdateDate);
     ofNullable(dto.getIssueCloseDate()).map(DateUtils::formatDateTime).ifPresent(issueBuilder::setCloseDate);
+    issueBuilder.setScope(Qualifiers.UNIT_TEST_FILE.equals(component.qualifier()) ? IssueScope.TEST.name() : IssueScope.MAIN.name());
   }
 
   private static String engineNameFrom(RuleKey ruleKey) {
index d8c15102dc89f029737fa372f4cbe42da743f111..e44cff2e985021f8c8f32d894d54c0375f64b31d 100644 (file)
@@ -113,7 +113,7 @@ public class SearchResponseLoader {
 
     if (issueKeysToLoad.isEmpty()) {
       return issueKeys.stream()
-        .map(new KeyToIssueFunction(preloadedIssues)::apply).filter(Objects::nonNull)
+        .map(new KeyToIssueFunction(preloadedIssues)).filter(Objects::nonNull)
         .collect(Collectors.toList());
     }
 
@@ -122,7 +122,7 @@ public class SearchResponseLoader {
       .collect(toList(preloadedIssues.size() + loadedIssues.size()));
 
     return issueKeys.stream()
-      .map(new KeyToIssueFunction(unorderedIssues)::apply).filter(Objects::nonNull)
+      .map(new KeyToIssueFunction(unorderedIssues)).filter(Objects::nonNull)
       .collect(Collectors.toList());
   }
 
index 066ed5b0c1e185ccb75032c6a0f1c00949c9e1b8..e04ffc28425205452e0cc839afee7cf4382be19a 100644 (file)
@@ -92,6 +92,7 @@ import static org.assertj.core.groups.Tuple.tuple;
 import static org.junit.rules.ExpectedException.none;
 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.server.ws.WebService.Param.FACETS;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
@@ -756,7 +757,129 @@ public class SearchActionTest {
     assertThat(ws.newRequest()
       .setMultiParam("author", singletonList("unknown"))
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-      .isEmpty();
+        .isEmpty();
+  }
+
+  @Test
+  public void filter_by_test_scope() {
+    OrganizationDto organization = db.organizations().insert(p -> p.setUuid("org-1").setKey("org-1"));
+    ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
+    indexPermissions();
+    ComponentDto mainCodeFile = db.components().insertComponent(
+      newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+    ComponentDto testCodeFile = db.components().insertComponent(
+      newFileDto(project, null, "ANOTHER_FILE_ID").setDbKey("ANOTHER_FILE_KEY").setQualifier(UNIT_TEST_FILE));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, mainCodeFile, project)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR");
+    IssueDto issue2 = newDto(rule, mainCodeFile, project)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR");
+    IssueDto issue3 = newDto(rule, testCodeFile, project)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-4037-b650-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR");
+    dbClient.issueDao().insert(session, issue1, issue2, issue3);
+    session.commit();
+    indexIssues();
+
+    ws.newRequest()
+      .setParam("scopes", "TEST")
+      .setParam(FACETS, "scopes")
+      .execute()
+      .assertJson(this.getClass(), "filter_by_test_scope.json");
+  }
+
+  @Test
+  public void filter_by_main_scope() {
+    OrganizationDto organization = db.organizations().insert(p -> p.setUuid("org-1").setKey("org-1"));
+    ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
+    indexPermissions();
+    ComponentDto mainCodeFile = db.components().insertComponent(
+      newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+    ComponentDto testCodeFile = db.components().insertComponent(
+      newFileDto(project, null, "ANOTHER_FILE_ID").setDbKey("ANOTHER_FILE_KEY").setQualifier(UNIT_TEST_FILE));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, mainCodeFile, project)
+      .setType(CODE_SMELL)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("83ec1d05-9397-4137-9978-85368bcc3b90")
+      .setSeverity("MAJOR");
+    IssueDto issue2 = newDto(rule, mainCodeFile, project)
+      .setType(CODE_SMELL)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR");
+    IssueDto issue3 = newDto(rule, testCodeFile, project)
+      .setType(CODE_SMELL)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("82fd47d4-4037-b650-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR");
+    dbClient.issueDao().insert(session, issue1, issue2, issue3);
+    session.commit();
+    indexIssues();
+
+    ws.newRequest()
+      .setParam("scopes", "MAIN")
+      .setParam(FACETS, "scopes")
+      .execute()
+      .assertJson(this.getClass(), "filter_by_main_scope.json");
+  }
+
+  @Test
+  public void filter_by_scope_always_returns_all_scope_facet_values() {
+    OrganizationDto organization = db.organizations().insert(p -> p.setUuid("org-1").setKey("org-1"));
+    ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
+    indexPermissions();
+    ComponentDto mainCodeFile = db.components().insertComponent(
+      newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+    RuleDto rule = newIssueRule();
+    IssueDto issue1 = newDto(rule, mainCodeFile, project)
+      .setType(CODE_SMELL)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("83ec1d05-9397-4137-9978-85368bcc3b90")
+      .setSeverity("MAJOR");
+    IssueDto issue2 = newDto(rule, mainCodeFile, project)
+      .setType(CODE_SMELL)
+      .setIssueCreationDate(parseDate("2014-09-04"))
+      .setIssueUpdateDate(parseDate("2017-12-04"))
+      .setEffort(10L)
+      .setStatus("OPEN")
+      .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2")
+      .setSeverity("MAJOR");
+    dbClient.issueDao().insert(session, issue1, issue2);
+    session.commit();
+    indexIssues();
+
+    ws.newRequest()
+      .setParam("scopes", "MAIN")
+      .setParam(FACETS, "scopes")
+      .execute()
+      .assertJson(this.getClass(), "filter_by_main_scope_2.json");
   }
 
   @Test
@@ -788,8 +911,8 @@ public class SearchActionTest {
       // This parameter will be ignored
       .setParam("authors", "leia")
       .executeProtobuf(SearchWsResponse.class).getIssuesList())
-      .extracting(Issue::getKey)
-      .containsExactlyInAnyOrder(issue2.getKey());
+        .extracting(Issue::getKey)
+        .containsExactlyInAnyOrder(issue2.getKey());
   }
 
   @Test
@@ -1069,8 +1192,8 @@ public class SearchActionTest {
     assertThatThrownBy(() -> ws.newRequest()
       .setParam("types", RuleType.SECURITY_HOTSPOT.toString())
       .execute())
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
   }
 
   @Test
@@ -1202,7 +1325,8 @@ public class SearchActionTest {
     assertThat(def.params()).extracting("key").containsExactlyInAnyOrder(
       "additionalFields", "asc", "assigned", "assignees", "authors", "author", "componentKeys", "branch",
       "pullRequest", "organization",
-      "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "languages", "moduleUuids", "onComponentOnly",
+      "createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "fileUuids", "issues", "scopes", "languages", "moduleUuids",
+      "onComponentOnly",
       "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod",
       "statuses", "tags", "types", "owaspTop10", "sansTop25", "cwe", "sonarsourceSecurity");
 
diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope.json
new file mode 100644 (file)
index 0000000..7fb9202
--- /dev/null
@@ -0,0 +1,76 @@
+{
+  "total": 2,
+  "p": 1,
+  "ps": 100,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2
+  },
+  "effortTotal": 20,
+  "debtTotal": 20,
+  "issues": [
+    {
+      "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "effort": "10min",
+      "debt": "10min",
+      "organization": "org-1",
+      "scope": "MAIN"
+    },
+    {
+      "key": "83ec1d05-9397-4137-9978-85368bcc3b90",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "effort": "10min",
+      "debt": "10min",
+      "organization": "org-1",
+      "scope": "MAIN"
+    }
+  ],
+  "components": [
+    {
+      "organization": "org-1",
+      "key": "FILE_KEY",
+      "uuid": "FILE_ID",
+      "enabled": true,
+      "qualifier": "FIL",
+      "name": "NAME_FILE_ID",
+      "longName": "null/NAME_FILE_ID",
+      "path": "null/NAME_FILE_ID"
+    },
+    {
+      "organization": "org-1",
+      "key": "PROJECT_KEY",
+      "uuid": "PROJECT_ID",
+      "enabled": true,
+      "qualifier": "TRK",
+      "name": "NAME_PROJECT_ID",
+      "longName": "LONG_NAME_PROJECT_ID"
+    }
+  ],
+  "facets": [
+    {
+      "property": "scopes",
+      "values": [
+        {
+          "val": "MAIN",
+          "count": 2
+        },
+        {
+          "val": "TEST",
+          "count": 1
+        }
+      ]
+    }
+  ]
+}
diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope_2.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope_2.json
new file mode 100644 (file)
index 0000000..9105ecb
--- /dev/null
@@ -0,0 +1,76 @@
+{
+  "total": 2,
+  "p": 1,
+  "ps": 100,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2
+  },
+  "effortTotal": 20,
+  "debtTotal": 20,
+  "issues": [
+    {
+      "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "effort": "10min",
+      "debt": "10min",
+      "organization": "org-1",
+      "scope": "MAIN"
+    },
+    {
+      "key": "83ec1d05-9397-4137-9978-85368bcc3b90",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "effort": "10min",
+      "debt": "10min",
+      "organization": "org-1",
+      "scope": "MAIN"
+    }
+  ],
+  "components": [
+    {
+      "organization": "org-1",
+      "key": "FILE_KEY",
+      "uuid": "FILE_ID",
+      "enabled": true,
+      "qualifier": "FIL",
+      "name": "NAME_FILE_ID",
+      "longName": "null/NAME_FILE_ID",
+      "path": "null/NAME_FILE_ID"
+    },
+    {
+      "organization": "org-1",
+      "key": "PROJECT_KEY",
+      "uuid": "PROJECT_ID",
+      "enabled": true,
+      "qualifier": "TRK",
+      "name": "NAME_PROJECT_ID",
+      "longName": "LONG_NAME_PROJECT_ID"
+    }
+  ],
+  "facets": [
+    {
+      "property": "scopes",
+      "values": [
+        {
+          "val": "MAIN",
+          "count": 2
+        },
+        {
+          "val": "TEST",
+          "count": 0
+        }
+      ]
+    }
+  ]
+}
diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_test_scope.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_test_scope.json
new file mode 100644 (file)
index 0000000..e2488d2
--- /dev/null
@@ -0,0 +1,63 @@
+{
+  "total": 1,
+  "p": 1,
+  "ps": 100,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 1
+  },
+  "effortTotal": 10,
+  "debtTotal": 10,
+  "issues": [
+    {
+      "key": "82fd47d4-4037-b650-80bc-7b112bd4eac2",
+      "rule": "xoo:x1",
+      "severity": "MAJOR",
+      "component": "ANOTHER_FILE_KEY",
+      "project": "PROJECT_KEY",
+      "flows": [],
+      "status": "OPEN",
+      "effort": "10min",
+      "debt": "10min",
+      "organization": "org-1",
+      "scope": "TEST"
+    }
+  ],
+  "components": [
+    {
+      "organization": "org-1",
+      "key": "ANOTHER_FILE_KEY",
+      "uuid": "ANOTHER_FILE_ID",
+      "enabled": true,
+      "qualifier": "UTS",
+      "name": "NAME_ANOTHER_FILE_ID",
+      "longName": "null/NAME_ANOTHER_FILE_ID",
+      "path": "null/NAME_ANOTHER_FILE_ID"
+    },
+    {
+      "organization": "org-1",
+      "key": "PROJECT_KEY",
+      "uuid": "PROJECT_ID",
+      "enabled": true,
+      "qualifier": "TRK",
+      "name": "NAME_PROJECT_ID",
+      "longName": "LONG_NAME_PROJECT_ID"
+    }
+  ],
+  "facets": [
+    {
+      "property": "scopes",
+      "values": [
+        {
+          "val": "TEST",
+          "count": 1
+        },
+        {
+          "val": "MAIN",
+          "count": 2
+        }
+      ]
+    }
+  ]
+}
index a0e290e7446df48e9ea77d703c02776ebbd55ee5..f4e7170e6c7573fce264371d3bcd7465bce98996 100644 (file)
@@ -80,6 +80,7 @@ public class IssuesWsParameters {
   public static final String DEPRECATED_PARAM_AUTHORS = "authors";
 
   public static final String PARAM_AUTHOR = "author";
+  public static final String PARAM_SCOPES = "scopes";
   public static final String PARAM_LANGUAGES = "languages";
   public static final String PARAM_TAGS = "tags";
   public static final String PARAM_TYPES = "types";
index 40c0921a813f8da02a8f99048f5d1c6d3f253c07..a6bcbf32a6d62c37bc4e76582d0dcd88b612c913 100644 (file)
@@ -154,6 +154,7 @@ message Issue {
 
   optional string externalRuleEngine = 33;
   optional bool fromHotspot = 34;
+  optional string scope = 35;
 }
 
 message Transitions {