From 52d805b3ab00fb499546f79fba0a852e893fea79 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Fri, 1 Dec 2017 16:34:41 +0100 Subject: [PATCH] SONAR-9332 api/issues/tags searches across organizations --- .../sonar/server/issue/index/IssueIndex.java | 12 +++++--- .../org/sonar/server/issue/ws/TagsAction.java | 19 +++++------- .../sonar/server/rule/index/RuleIndex.java | 11 +++++-- .../server/issue/index/IssueIndexTest.java | 3 ++ .../sonar/server/issue/ws/TagsActionTest.java | 25 ++++++++++++++-- .../sonarqube/tests/issue/IssueTagsTest.java | 30 +++++++++++++++---- 6 files changed, 74 insertions(+), 26 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java index ba7cd670e53..1affc61ee1a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -595,18 +595,22 @@ public class IssueIndex { .subAggregation(facetTopAggregation)); } - public List listTags(OrganizationDto organization, @Nullable String textQuery, int size) { + public List listTags(@Nullable OrganizationDto organization, @Nullable String textQuery, int size) { int maxPageSize = 500; checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize); if (size <= 0) { return emptyList(); } + BoolQueryBuilder esQuery = boolQuery() + .filter(createAuthorizationFilter(true)); + if (organization != null) { + esQuery.filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid())); + } + SearchRequestBuilder requestBuilder = client .prepareSearch(INDEX_TYPE_ISSUE) - .setQuery(boolQuery() - .filter(createAuthorizationFilter(true)) - .filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid()))) + .setQuery(esQuery) .setSize(0); TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME_FOR_TAGS) diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java index 0359656f0ed..f208ca97812 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java @@ -23,9 +23,9 @@ import com.google.common.io.Resources; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.SortedSet; import java.util.TreeSet; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -37,10 +37,9 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.organization.OrganizationDto; import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.rule.index.RuleIndex; -import org.sonar.server.ws.WsUtils; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION; /** @@ -52,13 +51,11 @@ public class TagsAction implements IssuesWsAction { private final IssueIndex issueIndex; private final RuleIndex ruleIndex; private final DbClient dbClient; - private final DefaultOrganizationProvider defaultOrganizationProvider; - public TagsAction(IssueIndex issueIndex, RuleIndex ruleIndex, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) { + public TagsAction(IssueIndex issueIndex, RuleIndex ruleIndex, DbClient dbClient) { this.issueIndex = issueIndex; this.ruleIndex = ruleIndex; this.dbClient = dbClient; - this.defaultOrganizationProvider = defaultOrganizationProvider; } private static void writeResponse(Response response, List tags) { @@ -95,7 +92,7 @@ public class TagsAction implements IssuesWsAction { writeResponse(response, tags); } - private List listTags(OrganizationDto organization, @Nullable String textQuery, int pageSize) { + private List listTags(@Nullable OrganizationDto organization, @Nullable String textQuery, int pageSize) { Collection issueTags = issueIndex.listTags(organization, textQuery, pageSize); Collection ruleTags = ruleIndex.listTags(organization, textQuery, pageSize); @@ -106,13 +103,11 @@ public class TagsAction implements IssuesWsAction { return resultAsList.size() > pageSize && pageSize > 0 ? resultAsList.subList(0, pageSize) : resultAsList; } + @CheckForNull private OrganizationDto getOrganization(@Nullable String organizationKey) { try (DbSession dbSession = dbClient.openSession(false)) { - String organizationOrDefaultKey = Optional.ofNullable(organizationKey) - .orElseGet(defaultOrganizationProvider.get()::getKey); - return WsUtils.checkFoundWithOptional( - dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey), - "No organization with key '%s'", organizationOrDefaultKey); + return organizationKey == null ? null + : checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'", organizationKey); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java index 87e6454194a..a7a76dd3c9e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -20,6 +20,7 @@ package org.sonar.server.rule.index; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -547,17 +548,21 @@ public class RuleIndex { return EsUtils.termsKeys(esResponse.getAggregations().get(AGGREGATION_NAME)); } - public List listTags(OrganizationDto organization, @Nullable String query, int size) { + public List listTags(@Nullable OrganizationDto organization, @Nullable String query, int size) { int maxPageSize = 500; checkArgument(size <= maxPageSize, "Page size must be lower than or equals to " + maxPageSize); if (size <= 0) { return emptyList(); } + ImmutableList.Builder scopes = ImmutableList.builder() + .add(RuleExtensionScope.system().getScope()); + if (organization != null) { + scopes.add(RuleExtensionScope.organization(organization).getScope()); + } TermsQueryBuilder scopeFilter = QueryBuilders.termsQuery( FIELD_RULE_EXTENSION_SCOPE, - RuleExtensionScope.system().getScope(), - RuleExtensionScope.organization(organization).getScope()); + scopes.build().toArray(new String[0])); TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME_FOR_TAGS) .field(FIELD_RULE_EXTENSION_TAGS) diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java index 25cf4657915..7cc4dce101d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java @@ -1200,9 +1200,11 @@ public class IssueIndexTest { ruleIndexer.commitAndIndex(db.getSession(), asList(r1.getKey(), r2.getKey())); OrganizationDto org = db.organizations().insert(); + OrganizationDto anotherOrg = db.organizations().insert(); ComponentDto project = ComponentTesting.newPrivateProjectDto(newOrganizationDto()); ComponentDto file = newFileDto(project, null); indexIssues( + newDoc("I42", file).setOrganizationUuid(anotherOrg.getUuid()).setRuleKey(r1.getKey().toString()).setTags(of("another")), newDoc("I1", file).setOrganizationUuid(org.getUuid()).setRuleKey(r1.getKey().toString()).setTags(of("convention", "java8", "bug")), newDoc("I2", file).setOrganizationUuid(org.getUuid()).setRuleKey(r1.getKey().toString()).setTags(of("convention", "bug")), newDoc("I3", file).setOrganizationUuid(org.getUuid()).setRuleKey(r2.getKey().toString()), @@ -1214,6 +1216,7 @@ public class IssueIndexTest { assertThat(underTest.listTags(org, null, 1)).containsOnly("bug"); assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug"); assertThat(underTest.listTags(org, "invalidRegexp[", 100)).isEmpty(); + assertThat(underTest.listTags(null, null, 100)).containsExactlyInAnyOrder("another", "convention", "java8", "bug"); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java index 3241e73902d..f2c36f9c23e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java @@ -37,7 +37,6 @@ import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueIndexDefinition; import org.sonar.server.issue.index.IssueIndexer; import org.sonar.server.issue.index.IssueIteratorFactory; -import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.permission.index.AuthorizationTypeSupport; import org.sonar.server.permission.index.PermissionIndexerDao; import org.sonar.server.permission.index.PermissionIndexerTester; @@ -70,7 +69,7 @@ public class TagsActionTest { private IssueIndex issueIndex = new IssueIndex(esTester.client(), System2.INSTANCE, userSession, new AuthorizationTypeSupport(userSession)); private RuleIndex ruleIndex = new RuleIndex(esTester.client(), System2.INSTANCE); - private WsActionTester ws = new WsActionTester(new TagsAction(issueIndex, ruleIndex, dbTester.getDbClient(), TestDefaultOrganizationProvider.from(dbTester))); + private WsActionTester ws = new WsActionTester(new TagsAction(issueIndex, ruleIndex, dbTester.getDbClient())); private OrganizationDto organization; @Before @@ -172,6 +171,19 @@ public class TagsActionTest { assertJson(result).isSimilarTo("{\"tags\":[]}"); } + @Test + public void without_organization_parameter_is_cross_organization() { + userSession.logIn(); + OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto anotherOrganization = dbTester.organizations().insert(); + insertIssueWithBrowsePermission(organization, insertRuleWithoutTags(), "tag1"); + insertIssueWithBrowsePermission(anotherOrganization, insertRuleWithoutTags(), "tag2"); + + String result = ws.newRequest().execute().getInput(); + + assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\"]}"); + } + @Test public void json_example() throws Exception { userSession.logIn(); @@ -223,12 +235,21 @@ public class TagsActionTest { return dbTester.rules().insert(setSystemTags()); } + private void insertIssueWithBrowsePermission(OrganizationDto organization, RuleDefinitionDto rule, String... tags) { + IssueDto issue = insertIssueWithoutBrowsePermission(organization, rule, tags); + grantAccess(issue); + } + private void insertIssueWithBrowsePermission(RuleDefinitionDto rule, String... tags) { IssueDto issue = insertIssueWithoutBrowsePermission(rule, tags); grantAccess(issue); } private IssueDto insertIssueWithoutBrowsePermission(RuleDefinitionDto rule, String... tags) { + return insertIssueWithoutBrowsePermission(organization, rule, tags); + } + + private IssueDto insertIssueWithoutBrowsePermission(OrganizationDto organization, RuleDefinitionDto rule, String... tags) { IssueDto issue = dbTester.issues().insertIssue(organization, i -> i.setRule(rule).setTags(asList(tags))); ComponentDto project = dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), issue.getProjectUuid()).get(); userSession.addProjectPermission(USER, project); diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java index 2e4f00e1885..caf1401cffc 100644 --- a/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java @@ -21,7 +21,6 @@ package org.sonarqube.tests.issue; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarScanner; -import org.sonarqube.tests.Category6Suite; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -30,7 +29,9 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonarqube.qa.util.Tester; +import org.sonarqube.tests.Category6Suite; import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.Projects.CreateWsResponse; import org.sonarqube.ws.Users.CreateWsResponse.User; import org.sonarqube.ws.client.issue.SearchRequest; import org.sonarqube.ws.client.permission.AddUserRequest; @@ -38,6 +39,7 @@ import org.sonarqube.ws.client.project.CreateRequest; import util.ItUtils; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.newProjectKey; @@ -73,14 +75,14 @@ public class IssueTagsTest { .setName(randomAlphabetic(10)) .setVisibility("private") .build()); - analyzeProject(projectKey); + analyzeProject(organization.getKey(), projectKey); String issue = tester.wsClient().issues().search(new SearchRequest()).getIssues(0).getKey(); tester.wsClient().issues().setTags(issue, "bla", "blubb"); String[] publicTags = {"bad-practice", "convention", "pitfall"}; String[] privateTags = {"bad-practice", "bla", "blubb", "convention", "pitfall"}; - String defaultOrganization = null; + String defaultOrganization = tester.organizations().getDefaultOrganization().getKey(); // anonymous must not see custom tags of private project { @@ -106,6 +108,24 @@ public class IssueTagsTest { } } + @Test + public void tags_across_organizations() { + Organization organization = tester.organizations().generate(); + Organization anotherOrganization = tester.organizations().generate(); + restoreProfile(orchestrator, IssueTagsTest.class.getResource("/issue/one-issue-per-line-profile.xml"), organization.getKey()); + restoreProfile(orchestrator, IssueTagsTest.class.getResource("/issue/one-issue-per-line-profile.xml"), anotherOrganization.getKey()); + CreateWsResponse.Project project = tester.projects().provision(organization); + CreateWsResponse.Project anotherProject = tester.projects().provision(anotherOrganization); + analyzeProject(organization.getKey(), project.getKey()); + analyzeProject(anotherOrganization.getKey(), anotherProject.getKey()); + String issue = tester.wsClient().issues().search(new SearchRequest().setProjectKeys(singletonList(project.getKey()))).getIssues(0).getKey(); + String anotherIssue = tester.wsClient().issues().search(new SearchRequest().setProjectKeys(singletonList(anotherProject.getKey()))).getIssues(0).getKey(); + tester.wsClient().issues().setTags(issue, "first-tag"); + tester.wsClient().issues().setTags(anotherIssue, "another-tag"); + + assertThat(tester.wsClient().issues().getTags(null).content()).contains("first-tag", "another-tag"); + } + private void addMemberToOrganization(User member) { tester.organizations().service().addMember(organization.getKey(), member.getLogin()); } @@ -130,10 +150,10 @@ public class IssueTagsTest { expectedTags); } - private void analyzeProject(String projectKey) { + private void analyzeProject(String organizationKey, String projectKey) { List keyValueProperties = new ArrayList<>(asList( "sonar.projectKey", projectKey, - "sonar.organization", organization.getKey(), + "sonar.organization", organizationKey, "sonar.profile", "one-issue-per-line-profile", "sonar.login", "admin", "sonar.password", "admin", "sonar.scm.disabled", "false")); -- 2.39.5