From 6b84d18a99ca282fc029d62f8807270537cf80ef Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Fri, 10 Jul 2020 15:13:44 +0200 Subject: [PATCH] SONAR-13597 Add new 'Scope' facet to list of issue facets --- .../org/sonar/server/issue/SearchRequest.java | 15 ++ .../sonar/server/issue/index/IssueDoc.java | 9 ++ .../issue/index/IssueIndexDefinition.java | 2 + .../index/IssueIteratorForSingleChunk.java | 6 +- .../sonar/server/issue/index/IssueScope.java | 27 ++++ .../sonar/server/issue/SearchRequestTest.java | 82 +++++++++++ .../server/issue/index/IssueIndexerTest.java | 52 ++++++- .../sonar/server/issue/IssueDocTesting.java | 2 + .../sonar/server/issue/index/IssueIndex.java | 6 + .../sonar/server/issue/index/IssueQuery.java | 12 ++ .../server/issue/index/IssueQueryFactory.java | 1 + .../issue/index/IssueQueryFactoryTest.java | 9 +- .../sonar/server/issue/ws/SearchAction.java | 12 +- .../server/issue/ws/SearchResponseFormat.java | 3 + .../server/issue/ws/SearchResponseLoader.java | 4 +- .../server/issue/ws/SearchActionTest.java | 136 +++++++++++++++++- .../filter_by_main_scope.json | 76 ++++++++++ .../filter_by_main_scope_2.json | 76 ++++++++++ .../filter_by_test_scope.json | 63 ++++++++ .../ws/client/issue/IssuesWsParameters.java | 1 + sonar-ws/src/main/protobuf/ws-issues.proto | 1 + 21 files changed, 580 insertions(+), 15 deletions(-) create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueScope.java create mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java create mode 100644 server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope.json create mode 100644 server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope_2.json create mode 100644 server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_test_scope.json diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java index cd3b8174d90..587b8fae9e1 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/SearchRequest.java @@ -44,6 +44,7 @@ public class SearchRequest { private List facets; private List fileUuids; private List issues; + private Set scopes; private List languages; private List moduleUuids; private Boolean onComponentOnly; @@ -67,6 +68,10 @@ public class SearchRequest { private List sonarsourceSecurity; private List cwe; + public SearchRequest() { + // nothing to do here + } + @CheckForNull public List getActionPlans() { return actionPlans; @@ -227,6 +232,16 @@ public class SearchRequest { return this; } + @CheckForNull + public Set getScopes() { + return scopes; + } + + public SearchRequest setScopes(@Nullable Collection scopes) { + this.scopes = scopes == null ? null : ImmutableSet.copyOf(scopes); + return this; + } + @CheckForNull public List getLanguages() { return languages; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java index c5854408c1d..f08ca6222f2 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java @@ -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; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java index fb9e19da2f0..d73bc110179 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java @@ -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(); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java index d77b094947c..d4749a46d89 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java @@ -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 index 00000000000..92e31216530 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueScope.java @@ -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 index 00000000000..8ce9da76e6d --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/SearchRequestTest.java @@ -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(); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java index 07da774c810..326e8399ef8 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java @@ -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); } diff --git a/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java b/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java index fbe410e598f..16340cc2ea5 100644 --- a/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java +++ b/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/IssueDocTesting.java @@ -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)); diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index db39fc3028e..943f35f8a6e 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -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()); diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java index 950d132b2ec..ededee7f2ac 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java @@ -75,6 +75,7 @@ public class IssueQuery { private final Collection rules; private final Collection assignees; private final Collection authors; + private final Collection scopes; private final Collection languages; private final Collection tags; private final Collection 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 scopes() { + return scopes; + } + public Collection languages() { return languages; } @@ -303,6 +309,7 @@ public class IssueQuery { private Collection rules; private Collection assigneeUuids; private Collection authors; + private Collection scopes; private Collection languages; private Collection tags; private Collection types; @@ -398,6 +405,11 @@ public class IssueQuery { return this; } + public Builder scopes(@Nullable Collection s) { + this.scopes = s; + return this; + } + public Builder languages(@Nullable Collection l) { this.languages = l; return this; diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java index 1b5acb2ce62..5103a86d1be 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java @@ -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()) diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java index 61d324734cd..72ed3d3c018 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java @@ -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()); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 032af08365a..f2ab8cee099 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -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 ISSUE_SCOPES = Arrays.stream(IssueScope.values()).map(Enum::name).collect(Collectors.toSet()); private static final EnumSet ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT)); static final List 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.
Requires the 'Browse' permission on the specified project(s)." - + "
When issue indexation is in progress returns 503 service unavailable HTTP code.") + + "
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)) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index 5092b8ee638..0b737cfcfd3 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -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) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java index d8c15102dc8..e44cff2e985 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java @@ -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()); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 066ed5b0c1e..e04ffc28425 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -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 index 00000000000..7fb92028da5 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope.json @@ -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 index 00000000000..9105ecbffb7 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_main_scope_2.json @@ -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 index 00000000000..e2488d26a4f --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_test_scope.json @@ -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 + } + ] + } + ] +} 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 a0e290e7446..f4e7170e6c7 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 @@ -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"; diff --git a/sonar-ws/src/main/protobuf/ws-issues.proto b/sonar-ws/src/main/protobuf/ws-issues.proto index 40c0921a813..a6bcbf32a6d 100644 --- a/sonar-ws/src/main/protobuf/ws-issues.proto +++ b/sonar-ws/src/main/protobuf/ws-issues.proto @@ -154,6 +154,7 @@ message Issue { optional string externalRuleEngine = 33; optional bool fromHotspot = 34; + optional string scope = 35; } message Transitions { -- 2.39.5