Browse Source

SONAR-11124 Add project parameter in tags ws

* Remove search of tags in rules index
* Refactor IssueIndex#searchTags to use same code as searchAuthors
* Add project to api/issues/tags
* Rename SonarCloudIssueAssignTest to OrganizationIssueAssignTest
tags/7.5
Julien Lancelot 5 years ago
parent
commit
cd96a36db6

+ 5
- 37
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

@@ -70,7 +70,6 @@ import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.es.BaseDoc;
import org.sonar.server.es.EsClient;
@@ -83,11 +82,9 @@ import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.user.UserSession;
import org.sonar.server.view.index.ViewIndexDefinition;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
@@ -232,7 +229,6 @@ public class IssueIndex {
}
}

public static final String AGGREGATION_NAME_FOR_TAGS = "tags__issues";
private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*";
// TODO to be documented
// TODO move to Facets ?
@@ -692,37 +688,9 @@ public class IssueIndex {
}
}

public List<String> 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());
if (organization != null) {
esQuery.filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid()));
}

SearchRequestBuilder requestBuilder = client
.prepareSearch(INDEX_TYPE_ISSUE)
.setQuery(esQuery)
.setSize(0);

TermsAggregationBuilder termsAggregation = AggregationBuilders.terms(AGGREGATION_NAME_FOR_TAGS)
.field(FIELD_ISSUE_TAGS)
.size(size)
.order(Terms.Order.term(true))
.minDocCount(1L);
if (textQuery != null) {
String escapedTextQuery = escapeSpecialRegexChars(textQuery);
termsAggregation.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapedTextQuery), null));
}
requestBuilder.addAggregation(termsAggregation);

SearchResponse searchResponse = requestBuilder.get();
Terms issuesResult = searchResponse.getAggregations().get(AGGREGATION_NAME_FOR_TAGS);
return EsUtils.termsKeys(issuesResult);
public List<String> searchTags(IssueQuery query, @Nullable String textQuery, int size) {
Terms terms = listTermsMatching(FIELD_ISSUE_TAGS, query, textQuery, Terms.Order.term(true), size);
return EsUtils.termsKeys(terms);
}

public Map<String, Long> countTags(IssueQuery query, int maxNumberOfTags) {
@@ -735,7 +703,7 @@ public class IssueIndex {
return EsUtils.termsKeys(terms);
}

private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int maxNumberOfTags) {
private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, Terms.Order termsOrder, int size) {
SearchRequestBuilder requestBuilder = client
.prepareSearch(INDEX_TYPE_ISSUE)
// Avoids returning search hits
@@ -745,7 +713,7 @@ public class IssueIndex {

TermsAggregationBuilder aggreg = AggregationBuilders.terms("_ref")
.field(fieldName)
.size(maxNumberOfTags)
.size(size)
.order(termsOrder)
.minDocCount(1L);
if (textQuery != null) {

+ 65
- 34
server/sonar-server/src/main/java/org/sonar/server/issue/ws/TagsAction.java View File

@@ -19,29 +19,33 @@
*/
package org.sonar.server.issue.ws;

import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.CheckForNull;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.NewAction;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.issue.index.IssueQuery;
import org.sonarqube.ws.Issues;

import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;

/**
* List issue tags matching a given query.
@@ -49,14 +53,17 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORG
*/
public class TagsAction implements IssuesWsAction {

private static final String PARAM_ORGANIZATION = "organization";
private static final String PARAM_PROJECT = "project";

private final IssueIndex issueIndex;
private final RuleIndex ruleIndex;
private final DbClient dbClient;
private final ComponentFinder componentFinder;

public TagsAction(IssueIndex issueIndex, RuleIndex ruleIndex, DbClient dbClient) {
public TagsAction(IssueIndex issueIndex, DbClient dbClient, ComponentFinder componentFinder) {
this.issueIndex = issueIndex;
this.ruleIndex = ruleIndex;
this.dbClient = dbClient;
this.componentFinder = componentFinder;
}

@Override
@@ -65,7 +72,8 @@ public class TagsAction implements IssuesWsAction {
.setHandler(this)
.setSince("5.1")
.setDescription("List tags matching a given query")
.setResponseExample(Resources.getResource(getClass(), "tags-example.json"));
.setResponseExample(Resources.getResource(getClass(), "tags-example.json"))
.setChangelog(new Change("7.4", "Result doesn't include rules tags anymore"));
action.createSearchQuery("misra", "tags");
action.createPageSize(10, 100);
action.createParam(PARAM_ORGANIZATION)
@@ -74,37 +82,60 @@ public class TagsAction implements IssuesWsAction {
.setInternal(true)
.setExampleValue("my-org")
.setSince("6.4");
action.createParam(PARAM_PROJECT)
.setDescription("Project key")
.setRequired(false)
.setExampleValue(KEY_PROJECT_EXAMPLE_001)
.setSince("7.4");
}

@Override
public void handle(Request request, Response response) throws Exception {
String query = request.param(Param.TEXT_QUERY);
int pageSize = request.mandatoryParamAsInt("ps");
OrganizationDto organization = getOrganization(request.param(PARAM_ORGANIZATION));
List<String> tags = listTags(organization, query, pageSize == 0 ? Integer.MAX_VALUE : pageSize);
Issues.TagsResponse.Builder tagsResponseBuilder = Issues.TagsResponse.newBuilder();
tags.forEach(tagsResponseBuilder::addTags);
writeProtobuf(tagsResponseBuilder.build(), request, response);
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<OrganizationDto> organization = getOrganization(dbSession, request.param(PARAM_ORGANIZATION));
Optional<ComponentDto> project = getProject(dbSession, organization, request.param(PARAM_PROJECT));
List<String> tags = searchTags(organization, project, request);
Issues.TagsResponse.Builder tagsResponseBuilder = Issues.TagsResponse.newBuilder();
tags.forEach(tagsResponseBuilder::addTags);
writeProtobuf(tagsResponseBuilder.build(), request, response);
}
}

private List<String> listTags(@Nullable OrganizationDto organization, @Nullable String textQuery, int pageSize) {
Collection<String> issueTags = issueIndex.listTags(organization, textQuery, pageSize);
Collection<String> ruleTags = ruleIndex.listTags(organization, textQuery, pageSize);

SortedSet<String> result = new TreeSet<>();
result.addAll(issueTags);
result.addAll(ruleTags);
List<String> resultAsList = new ArrayList<>(result);
return resultAsList.size() > pageSize && pageSize > 0 ? resultAsList.subList(0, pageSize) : resultAsList;
private Optional<OrganizationDto> getOrganization(DbSession dbSession, @Nullable String organizationKey) {
return organizationKey == null ? Optional.empty()
: Optional.of(checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'", organizationKey));
}

@CheckForNull
private OrganizationDto getOrganization(@Nullable String organizationKey) {
try (DbSession dbSession = dbClient.openSession(false)) {
return organizationKey == null ? null
: checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'", organizationKey);
private Optional<ComponentDto> getProject(DbSession dbSession, Optional<OrganizationDto> organization, @Nullable String projectKey) {
if (projectKey == null) {
return Optional.empty();
}
ComponentDto project = componentFinder.getByKey(dbSession, projectKey);
checkArgument(project.scope().equals(Scopes.PROJECT), "Component '%s' must be a project", projectKey);
organization.ifPresent(o -> checkArgument(project.getOrganizationUuid().equals(o.getUuid()), "Project '%s' is not part of the organization '%s'", projectKey, o.getKey()));
return Optional.of(project);
}

private List<String> searchTags(Optional<OrganizationDto> organization, Optional<ComponentDto> project, Request request) {
IssueQuery.Builder issueQueryBuilder = IssueQuery.builder();
organization.ifPresent(o -> issueQueryBuilder.organizationUuid(o.getUuid()));
project.ifPresent(p -> {
switch (p.qualifier()) {
case Qualifiers.PROJECT:
issueQueryBuilder.projectUuids(ImmutableSet.of(p.uuid()));
return;
case Qualifiers.APP:
case Qualifiers.VIEW:
issueQueryBuilder.viewUuids(ImmutableSet.of(p.uuid()));
return;
default:
throw new IllegalArgumentException(String.format("Component of type '%s' is not supported", p.qualifier()));
}
});
return issueIndex.searchTags(
issueQueryBuilder.build(),
request.param(TEXT_QUERY),
request.mandatoryParamAsInt(PAGE_SIZE));
}

}

+ 7
- 17
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java View File

@@ -226,23 +226,13 @@ public class IssueIndexTest {
newDoc("I3", file).setOrganizationUuid(org.getUuid()).setRuleId(r2.getId()),
newDoc("I4", file).setOrganizationUuid(org.getUuid()).setRuleId(r1.getId()).setTags(of("convention")));

assertThat(underTest.listTags(org, null, 100)).containsOnly("convention", "java8", "bug");
assertThat(underTest.listTags(org, null, 2)).containsOnly("bug", "convention");
assertThat(underTest.listTags(org, "vent", 100)).containsOnly("convention");
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
public void fail_to_list_tags_when_size_greater_than_500() {
OrganizationDto organization = db.organizations().insert();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Page size must be lower than or equals to 500");

underTest.listTags(organization, null, 501);
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), null, 100)).containsOnly("convention", "java8", "bug");
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), null, 2)).containsOnly("bug", "convention");
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), "vent", 100)).containsOnly("convention");
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), null, 1)).containsOnly("bug");
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), null, 100)).containsOnly("convention", "java8", "bug");
assertThat(underTest.searchTags(IssueQuery.builder().organizationUuid(org.getUuid()).build(), "invalidRegexp[", 100)).isEmpty();
assertThat(underTest.searchTags(IssueQuery.builder().build(), null, 100)).containsExactlyInAnyOrder("another", "convention", "java8", "bug");
}

@Test

+ 200
- 153
server/sonar-server/src/test/java/org/sonar/server/issue/ws/TagsActionTest.java View File

@@ -19,35 +19,38 @@
*/
package org.sonar.server.issue.ws;

import java.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.server.ws.WebService.Action;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
import org.sonar.server.permission.index.IndexPermissions;
import org.sonar.server.permission.index.PermissionIndexerTester;
import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.view.index.ViewIndexer;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Issues.TagsResponse;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.db.rule.RuleTesting.setSystemTags;
import static org.sonar.db.rule.RuleTesting.setTags;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
import static org.sonar.test.JsonAssert.assertJson;

public class TagsActionTest {
@@ -55,144 +58,232 @@ public class TagsActionTest {
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester dbTester = DbTester.create();
public DbTester db = DbTester.create();
@Rule
public EsTester es = EsTester.create();
@Rule
public ExpectedException expectedException = ExpectedException.none();

private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbTester.getDbClient(), new IssueIteratorFactory(dbTester.getDbClient()));
private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient());
private PermissionIndexerTester permissionIndexerTester = new PermissionIndexerTester(es, issueIndexer);
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
private RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE);
private WsActionTester ws = new WsActionTester(new TagsAction(issueIndex, ruleIndex, dbTester.getDbClient()));
private OrganizationDto organization;
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer);
private ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT);

@Before
public void before() {
organization = dbTester.organizations().insert();
}
private WsActionTester ws = new WsActionTester(new TagsAction(issueIndex, db.getDbClient(), new ComponentFinder(db.getDbClient(), resourceTypes)));

@Test
public void return_tags_from_issues() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag1", "tag2");
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag3", "tag4", "tag5");
public void search_tags() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag3", "tag4", "tag5")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project);

String result = ws.newRequest()
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]}");
TagsResponse result = ws.newRequest().executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("tag1", "tag2", "tag3", "tag4", "tag5");
}

@Test
public void return_tags_from_rules() {
userSession.logIn();
RuleDefinitionDto r = dbTester.rules().insert(setSystemTags("tag1"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId());
dbTester.rules().insertOrUpdateMetadata(r, organization, setTags("tag2"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId(), organization);
RuleDefinitionDto r2 = dbTester.rules().insert(setSystemTags("tag3"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r2.getId());
dbTester.rules().insertOrUpdateMetadata(r2, organization, setTags("tag4", "tag5"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r2.getId(), organization);
public void search_tags_by_query() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag12", "tag4", "tag5")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project);
TagsResponse result = ws.newRequest()
.setParam("q", "ag1")
.executeProtobuf(TagsResponse.class);

String result = ws.newRequest()
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]}");
assertThat(result.getTagsList()).containsExactly("tag1", "tag12");
}

@Test
public void return_tags_from_issue_and_rule_tags() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag1", "tag2");
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag3", "tag4", "tag5");

RuleDefinitionDto r = dbTester.rules().insert(setSystemTags("tag6"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId());
dbTester.rules().insertOrUpdateMetadata(r, organization, setTags("tag7"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId(), organization);
public void search_tags_by_organization() {
RuleDefinitionDto rule = db.rules().insert();
// Tags on issues of organization 1
OrganizationDto organization1 = db.organizations().insert();
ComponentDto project1 = db.components().insertPrivateProject(organization1);
db.issues().insert(rule, project1, project1, issue -> issue.setTags(asList("tag1", "tag2")));
// Tags on issues of organization 2
OrganizationDto organization2 = db.organizations().insert();
ComponentDto project2 = db.components().insertPrivateProject(organization2);
db.issues().insert(rule, project2, project2, issue -> issue.setTags(singletonList("tag3")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project1, project2);

TagsResponse result = ws.newRequest()
.setParam("organization", organization1.getKey())
.executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("tag1", "tag2");
}

String result = ws.newRequest()
@Test
public void search_tags_by_project() {
RuleDefinitionDto rule = db.rules().insert();
OrganizationDto organization = db.organizations().insert();
ComponentDto project1 = db.components().insertPrivateProject(organization);
ComponentDto project2 = db.components().insertPrivateProject(organization);
db.issues().insert(rule, project1, project1, issue -> issue.setTags(singletonList("tag1")));
db.issues().insert(rule, project2, project2, issue -> issue.setTags(singletonList("tag2")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project1, project2);

TagsResponse result = ws.newRequest()
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\", \"tag6\", \"tag7\"]}");
.setParam("project", project1.getKey())
.executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("tag1");
}

@Test
public void return_limited_size() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag1", "tag2");
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag3", "tag4", "tag5");
public void search_tags_by_portfolio() {
OrganizationDto organization = db.getDefaultOrganization();
ComponentDto portfolio = db.components().insertPrivatePortfolio(organization);
ComponentDto project = db.components().insertPrivateProject(organization);
db.components().insertComponent(newProjectCopy(project, portfolio));
permissionIndexer.allowOnlyAnyone(project);
RuleDefinitionDto rule = db.rules().insert();
db.issues().insert(rule, project, project, issue -> issue.setTags(singletonList("cwe")));
issueIndexer.indexOnStartup(emptySet());
viewIndexer.indexOnStartup(emptySet());
userSession.logIn().addMembership(organization);

TagsResponse result = ws.newRequest()
.setParam("project", portfolio.getKey())
.executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("cwe");
}

String result = ws.newRequest()
.setParam("ps", "2")
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\"]}");
@Test
public void search_tags_by_application() {
OrganizationDto organization = db.getDefaultOrganization();
ComponentDto application = db.components().insertPrivateApplication(organization);
ComponentDto project = db.components().insertPrivateProject(organization);
db.components().insertComponent(newProjectCopy(project, application));
permissionIndexer.allowOnlyAnyone(project);
RuleDefinitionDto rule = db.rules().insert();
db.issues().insert(rule, project, project, issue -> issue.setTags(singletonList("cwe")));
issueIndexer.indexOnStartup(emptySet());
viewIndexer.indexOnStartup(emptySet());
userSession.logIn().addMembership(organization);

TagsResponse result = ws.newRequest()
.setParam("project", application.getKey())
.executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("cwe");
}

@Test
public void return_tags_matching_query() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag1", "tag2");
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag12", "tag4", "tag5");
public void return_limited_size() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag1", "tag2")));
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("tag3", "tag4", "tag5")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project);

TagsResponse result = ws.newRequest()
.setParam("ps", "2")
.executeProtobuf(TagsResponse.class);

String result = ws.newRequest()
.setParam("q", "ag1")
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag12\"]}");
assertThat(result.getTagsList()).containsExactly("tag1", "tag2");
}

@Test
public void do_not_return_issues_without_permission() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "tag1", "tag2");
insertIssueWithoutBrowsePermission(insertRuleWithoutTags(), "tag3", "tag4");
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project1 = db.components().insertPrivateProject();
ComponentDto project2 = db.components().insertPrivateProject();
db.issues().insert(rule, project1, project1, issue -> issue.setTags(asList("tag1", "tag2")));
db.issues().insert(rule, project2, project2, issue -> issue.setTags(asList("tag3", "tag4", "tag5")));
issueIndexer.indexOnStartup(emptySet());
// Project 2 is not visible to current user
permissionIndexer.allowOnlyAnyone(project1);

TagsResponse result = ws.newRequest().executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("tag1", "tag2");
}

String result = ws.newRequest()
.setParam("organization", organization.getKey())
.execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\"]}");
assertThat(result).doesNotContain("tag3").doesNotContain("tag4");
@Test
public void without_organization_parameter_is_cross_organization() {
RuleDefinitionDto rule = db.rules().insert();
// Tags on issues of organization 1
OrganizationDto organization1 = db.organizations().insert();
ComponentDto project1 = db.components().insertPrivateProject(organization1);
db.issues().insert(rule, project1, project1, issue -> issue.setTags(asList("tag1", "tag2")));
// Tags on issues of organization 2
OrganizationDto organization2 = db.organizations().insert();
ComponentDto project2 = db.components().insertPrivateProject(organization2);
db.issues().insert(rule, project2, project2, issue -> issue.setTags(singletonList("tag3")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project1, project2);

TagsResponse result = ws.newRequest().executeProtobuf(TagsResponse.class);

assertThat(result.getTagsList()).containsExactly("tag1", "tag2", "tag3");
}

@Test
public void empty_list() {
userSession.logIn();
String result = ws.newRequest().execute().getInput();
assertJson(result).isSimilarTo("{\"tags\":[]}");
TagsResponse result = ws.newRequest().executeProtobuf(TagsResponse.class);
assertThat(result.getTagsList()).isEmpty();
}

@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");
public void fail_when_project_does_not_belong_to_organization() {
OrganizationDto organization = db.organizations().insert();
OrganizationDto otherOrganization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(otherOrganization);
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project, project);

String result = ws.newRequest().execute().getInput();
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(format("Project '%s' is not part of the organization '%s'", project.getKey(), organization.getKey()));

assertJson(result).isSimilarTo("{\"tags\":[\"tag1\", \"tag2\"]}");
ws.newRequest()
.setParam("organization", organization.getKey())
.setParam("project", project.getKey())
.execute();
}

@Test
public void json_example() {
userSession.logIn();
insertIssueWithBrowsePermission(insertRuleWithoutTags(), "convention");
public void fail_when_project_parameter_does_not_match_a_project() {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(organization);
ComponentDto file = db.components().insertComponent(newFileDto(project));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project, project);

RuleDefinitionDto r = dbTester.rules().insert(setSystemTags("cwe"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId());
dbTester.rules().insertOrUpdateMetadata(r, organization, setTags("security"));
ruleIndexer.commitAndIndex(dbTester.getSession(), r.getId(), organization);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(format("Component '%s' must be a project", file.getKey()));

String result = ws.newRequest()
ws.newRequest()
.setParam("organization", organization.getKey())
.execute().getInput();
.setParam("project", file.getKey())
.execute();
}

@Test
public void json_example() {
RuleDefinitionDto rule = db.rules().insert();
ComponentDto project = db.components().insertPrivateProject();
db.issues().insert(rule, project, project, issue -> issue.setTags(asList("convention", "security")));
db.issues().insert(rule, project, project, issue -> issue.setTags(singletonList("cwe")));
issueIndexer.indexOnStartup(emptySet());
permissionIndexer.allowOnlyAnyone(project);

String result = ws.newRequest().execute().getInput();

assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
}
@@ -205,57 +296,13 @@ public class TagsActionTest {
assertThat(action.responseExampleAsString()).isNotEmpty();
assertThat(action.isPost()).isFalse();
assertThat(action.isInternal()).isFalse();
assertThat(action.params()).extracting(Param::key).containsExactlyInAnyOrder("q", "ps", "organization");

Param query = action.param("q");
assertThat(query.isRequired()).isFalse();
assertThat(query.description()).isNotEmpty();
assertThat(query.exampleValue()).isNotEmpty();

Param pageSize = action.param("ps");
assertThat(pageSize.isRequired()).isFalse();
assertThat(pageSize.defaultValue()).isEqualTo("10");
assertThat(pageSize.description()).isNotEmpty();
assertThat(pageSize.exampleValue()).isNotEmpty();

Param organization = action.param("organization");
assertThat(organization).isNotNull();
assertThat(organization.isRequired()).isFalse();
assertThat(organization.description()).isNotEmpty();
assertThat(organization.exampleValue()).isNotEmpty();
assertThat(organization.isInternal()).isTrue();
assertThat(organization.since()).isEqualTo("6.4");
}

private RuleDefinitionDto insertRuleWithoutTags() {
return dbTester.rules().insert(setSystemTags());
}

private void insertIssueWithBrowsePermission(OrganizationDto organization, RuleDefinitionDto rule, String... tags) {
IssueDto issue = insertIssueWithoutBrowsePermission(organization, rule, tags);
grantAccess(issue);
assertThat(action.params())
.extracting(Param::key, Param::defaultValue, Param::since, Param::isRequired, Param::isInternal)
.containsExactlyInAnyOrder(
tuple("q", null, null, false, false),
tuple("ps", "10", null, false, false),
tuple("organization", null, "6.4", false, true),
tuple("project", null, "7.4", false, false));
}

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);
issueIndexer.commitAndIndexIssues(dbTester.getSession(), Collections.singletonList(issue));
return issue;
}

private void grantAccess(IssueDto issue) {
IndexPermissions access = new IndexPermissions(issue.getProjectUuid(), "TRK");
access.addUserId(userSession.getUserId());
permissionIndexerTester.allow(access);
}
}

+ 1
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java View File

@@ -307,6 +307,7 @@ public class IssuesService extends BaseService {
return call(
new GetRequest(path("tags"))
.setParam("organization", request.getOrganization())
.setParam("project", request.getProject())
.setParam("ps", request.getPs())
.setParam("q", request.getQ()),
TagsResponse.parser());

+ 13
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/issues/TagsRequest.java View File

@@ -31,6 +31,7 @@ import javax.annotation.Generated;
public class TagsRequest {

private String organization;
private String project;
private String ps;
private String q;

@@ -47,6 +48,18 @@ public class TagsRequest {
return organization;
}

/**
* Example value: "my_project"
*/
public TagsRequest setProject(String project) {
this.project = project;
return this;
}

public String getProject() {
return project;
}

/**
* Example value: "20"
*/

Loading…
Cancel
Save