]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5531 Support paging, querying by rules and languages in Issues search WS
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 22 Sep 2014 15:10:27 +0000 (17:10 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 22 Sep 2014 16:22:45 +0000 (18:22 +0200)
22 files changed:
server/sonar-server/src/main/java/org/sonar/server/batch/UploadReportAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueDoc.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueNormalizer.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/search/ws/SearchRequestHandler.java
server/sonar-server/src/test/java/org/sonar/server/batch/UploadReportActionMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/db/IssueDaoTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsMediumTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/empty_result.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/single_result.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/paging.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/single_result.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java

index 547accceb0380c26d45b33f05b759c0db2dff72e..f8c670f073358977babe4b4defe2f5ecdba45a66 100644 (file)
@@ -41,7 +41,6 @@ public class UploadReportAction implements RequestHandler {
   public static final String UPLOAD_REPORT_ACTION = "upload_report";
 
   static final String PARAM_PROJECT = "project";
-  static final String PARAM_FIRST_ANALYSIS = "firstAnalysis";
 
   private final DbClient dbClient;
   private final IndexClient index;
@@ -68,12 +67,6 @@ public class UploadReportAction implements RequestHandler {
       .setRequired(true)
       .setDescription("Project key")
       .setExampleValue("org.codehaus.sonar:sonar");
-
-    action
-      .createParam(PARAM_FIRST_ANALYSIS)
-      .setDescription("Is it the first analysis of this project ?")
-      .setDefaultValue(false)
-      .setBooleanPossibleValues();
   }
 
   @Override
@@ -88,11 +81,9 @@ public class UploadReportAction implements RequestHandler {
         AuthorizedComponentDto project = dbClient.componentDao().getAuthorizedComponentByKey(projectKey, session);
 
         // Synchronize project permission indexes
-        boolean isFirstAnalysis = request.mandatoryParamAsBoolean(PARAM_FIRST_ANALYSIS);
-        if (isFirstAnalysis) {
-          permissionService.synchronizePermissions(session, project.key());
-          session.commit();
-        }
+        // TODO only update permission if no permission for a project
+        permissionService.synchronizePermissions(session, project.key());
+        session.commit();
 
         // Index project's issues
         dbClient.issueDao().synchronizeAfter(session,
index c0552d9c752d8b58be087fc5eb0f1009f77a7771..f025ccbd874c6d33c4b4aed2d503880d687bdb7f 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.server.search.BaseDoc;
 import org.sonar.server.search.IndexUtils;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -50,9 +51,12 @@ public class IssueDoc extends BaseDoc implements Issue {
 
   @Override
   public RuleKey ruleKey() {
-    return RuleKey.of(
-      (String) getField(IssueNormalizer.IssueField.REPOSITORY.field()),
-      (String) getField(IssueNormalizer.IssueField.RULE.field()));
+    return RuleKey.parse((String) getField(IssueNormalizer.IssueField.RULE_KEY.field()));
+  }
+
+  @Override
+  public String language() {
+    return getField(IssueNormalizer.IssueField.LANGUAGE.field());
   }
 
   @Override
index f5b67dfca6dada69942c1b30cfc8d32ad57ec6a8..7825074a811d6c233448b6148dd3314b1a803acb 100644 (file)
@@ -35,6 +35,8 @@ import org.sonar.api.web.UserRole;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.server.search.*;
 
+import javax.annotation.Nullable;
+
 import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
@@ -172,16 +174,15 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
     }
 
     // Field Filters
+    matchFilter(esFilter, IssueNormalizer.IssueField.KEY, query.issueKeys());
     matchFilter(esFilter, IssueNormalizer.IssueField.ACTION_PLAN, query.actionPlans());
     matchFilter(esFilter, IssueNormalizer.IssueField.ASSIGNEE, query.assignees());
     matchFilter(esFilter, IssueNormalizer.IssueField.PROJECT, query.componentRoots());
     matchFilter(esFilter, IssueNormalizer.IssueField.COMPONENT, query.components());
-    matchFilter(esFilter, IssueNormalizer.IssueField.KEY, query.issueKeys());
-    // TODO need to either materialize the language or join with rule
-    // query.languages(esFilter, IssueNormalizer.IssueField.L, query.issueKeys());
+    matchFilter(esFilter, IssueNormalizer.IssueField.LANGUAGE, query.languages());
     matchFilter(esFilter, IssueNormalizer.IssueField.RESOLUTION, query.resolutions());
     matchFilter(esFilter, IssueNormalizer.IssueField.REPORTER, query.reporters());
-    matchFilter(esFilter, IssueNormalizer.IssueField.RULE, query.rules());
+    matchFilter(esFilter, IssueNormalizer.IssueField.RULE_KEY, query.rules());
     matchFilter(esFilter, IssueNormalizer.IssueField.SEVERITY, query.severities());
     matchFilter(esFilter, IssueNormalizer.IssueField.STATUS, query.statuses());
 
@@ -227,7 +228,7 @@ public class IssueIndex extends BaseIndex<Issue, IssueDto, String> {
     return new Result<Issue>(this, response);
   }
 
-  private void matchFilter(BoolFilterBuilder filter, IndexField field, Collection<?> values) {
+  private void matchFilter(BoolFilterBuilder filter, IndexField field, @Nullable Collection<?> values) {
     if (values != null && !values.isEmpty()) {
       filter.must(FilterBuilders.termsFilter(field.field(), values));
     }
index 4fc6977e070a60c3f44bd13cf27b9ab6f5a60fd0..ba91903c0e9007c1338f82d33f3b460e5ba1271f 100644 (file)
@@ -60,9 +60,8 @@ public class IssueNormalizer extends BaseNormalizer<IssueDto, String> {
     public static final IndexField REPORTER = add(IndexField.Type.STRING, "reporter");
     public static final IndexField STATUS = add(IndexField.Type.STRING, "status");
     public static final IndexField SEVERITY = add(IndexField.Type.STRING, "severity");
-
-    public static final IndexField RULE = addSearchable(IndexField.Type.STRING, "rule");
-    public static final IndexField REPOSITORY = addSearchable(IndexField.Type.STRING, "repository");
+    public static final IndexField LANGUAGE = add(IndexField.Type.STRING, "language");
+    public static final IndexField RULE_KEY = addSearchable(IndexField.Type.STRING, "ruleKey");
 
     public static final Set<IndexField> ALL_FIELDS = getAllFields();
 
@@ -115,10 +114,8 @@ public class IssueNormalizer extends BaseNormalizer<IssueDto, String> {
     update.put(IssueField.STATUS.field(), dto.getStatus());
     update.put(IssueField.SEVERITY.field(), dto.getSeverity());
     update.put(IssueField.DEBT.field(), dto.getDebt());
-
-    // issueDoc.ruleKey();
-    update.put(IssueField.RULE.field(), dto.getRule());
-    update.put(IssueField.REPOSITORY.field(), dto.getRuleRepo());
+    update.put(IssueField.LANGUAGE.field(), dto.getLanguage());
+    update.put(IssueField.RULE_KEY.field(), dto.getRuleKey().toString());
 
     // TODO Not yet normalized
     // IssueDoc issueDoc = new IssueDoc(null);
index 30fd11a0cc292659170981c60a0963b663502223..56c07d46e87f921cfc6cd44dad749e6bdbefe31e 100644 (file)
@@ -167,19 +167,12 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> {
     action.createParam(IssueFilterParameters.CREATED_BEFORE)
       .setDescription("To retrieve issues created before the given date (exclusive). Format: date or datetime ISO formats")
       .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)");
-    action.createParam(IssueFilterParameters.PAGE_SIZE)
-      .setDescription("Maximum number of results per page. " +
-        "Default value: 100 (except when the 'components' parameter is set, value is set to \"-1\" in this case). " +
-        "If set to \"-1\", the max possible value is used")
-      .setExampleValue("50");
-    action.createParam(IssueFilterParameters.PAGE_INDEX)
-      .setDescription("Index of the selected page")
-      .setDefaultValue("1")
-      .setExampleValue("2");
-    action.createParam(IssueFilterParameters.SORT)
+    action.createParam(SearchRequestHandler.PARAM_SORT)
       .setDescription("Sort field")
+      .setDeprecatedKey(IssueFilterParameters.SORT)
       .setPossibleValues(IssueQuery.SORTS);
-    action.createParam(IssueFilterParameters.ASC)
+    action.createParam(SearchRequestHandler.PARAM_ASCENDING)
+      .setDeprecatedKey(IssueFilterParameters.ASC)
       .setDescription("Ascending sort")
       .setBooleanPossibleValues();
     action.createParam("format")
@@ -207,19 +200,18 @@ public class SearchAction extends SearchRequestHandler<IssueQuery, Issue> {
       .hideRules(request.paramAsBoolean(IssueFilterParameters.HIDE_RULES))
       .createdAt(request.paramAsDateTime(IssueFilterParameters.CREATED_AT))
       .createdAfter(request.paramAsDateTime(IssueFilterParameters.CREATED_AFTER))
-      .createdBefore(request.paramAsDateTime(IssueFilterParameters.CREATED_BEFORE))
-      .pageSize(request.paramAsInt(IssueFilterParameters.PAGE_SIZE))
-      .pageIndex(request.paramAsInt(IssueFilterParameters.PAGE_INDEX));
-    String sort = request.param(IssueFilterParameters.SORT);
+      .createdBefore(request.paramAsDateTime(IssueFilterParameters.CREATED_BEFORE));
+    String sort = request.param(SearchRequestHandler.PARAM_SORT);
     if (!Strings.isNullOrEmpty(sort)) {
       builder.sort(sort);
-      builder.asc(request.paramAsBoolean(IssueFilterParameters.ASC));
+      builder.asc(request.paramAsBoolean(SearchRequestHandler.PARAM_ASCENDING));
     }
     return builder.build();
   }
 
   @Override
   protected Result<Issue> doSearch(IssueQuery query, QueryContext context) {
+    // Set limit to context ?
     return ((DefaultIssueService)service).search(query, context);
   }
 
index 0379aedf1b1db11f4712e32878627b7c9068cd03..4ccc0d56aca088858deadfef75966569a6ecd938 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.server.search.QueryContext;
 import org.sonar.server.search.Result;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -44,8 +45,6 @@ public abstract class SearchRequestHandler<QUERY, DOMAIN> implements RequestHand
 
   public static final String PARAM_FACETS = "facets";
 
-  private int pageSize;
-  private int page;
   private List<String> fields;
 
   private final String actionName;
@@ -79,15 +78,17 @@ public abstract class SearchRequestHandler<QUERY, DOMAIN> implements RequestHand
 
     action
       .createParam(PARAM_PAGE)
+      .setDeprecatedKey("pageIndex")
       .setDescription("1-based page number")
       .setExampleValue("42")
       .setDefaultValue("1");
 
     action
       .createParam(PARAM_PAGE_SIZE)
+      .setDeprecatedKey("pageSize")
       .setDescription("Page size. Must be greater than 0.")
       .setExampleValue("20")
-      .setDefaultValue(String.valueOf(QueryContext.DEFAULT_LIMIT));
+      .setDefaultValue("100");
 
     action.createParam(PARAM_FACETS)
       .setDescription("Compute predefined facets")
@@ -114,7 +115,7 @@ public abstract class SearchRequestHandler<QUERY, DOMAIN> implements RequestHand
     Result<DOMAIN> result = doSearch(query, context);
 
     JsonWriter json = response.newJsonWriter().beginObject();
-    this.writeStatistics(json, result);
+    this.writeStatistics(json, result, context);
     doResultResponse(request, context, result, json);
     doContextResponse(request, context, result, json);
     if (context.isFacet()) {
@@ -130,10 +131,10 @@ public abstract class SearchRequestHandler<QUERY, DOMAIN> implements RequestHand
         request.mandatoryParamAsInt(PARAM_PAGE_SIZE));
   }
 
-  protected void writeStatistics(JsonWriter json, Result searchResult) {
+  protected void writeStatistics(JsonWriter json, Result searchResult, QueryContext context) {
     json.prop("total", searchResult.getTotal());
-    json.prop(PARAM_PAGE, page);
-    json.prop(PARAM_PAGE_SIZE, pageSize);
+    json.prop(PARAM_PAGE, context.getPage());
+    json.prop(PARAM_PAGE_SIZE, context.getLimit());
   }
 
   protected void writeFacets(Result<?> results, JsonWriter json) {
index 2f2fb36c0242ea8b205c633b1b95260ea31ce5fc..73d2a07459732e36071ed12fc493c733d885969e 100644 (file)
@@ -86,11 +86,11 @@ public class UploadReportActionMediumTest {
   public void define() throws Exception {
     WebService.Action restoreProfiles = controller.action(UploadReportAction.UPLOAD_REPORT_ACTION);
     assertThat(restoreProfiles).isNotNull();
-    assertThat(restoreProfiles.params()).hasSize(2);
+    assertThat(restoreProfiles.params()).hasSize(1);
   }
 
   @Test
-  public void add_project_issue_permission_index_on_first_analysis() throws Exception {
+  public void add_project_issue_permission_index() throws Exception {
     ComponentDto project = new ComponentDto()
       .setId(1L)
       .setKey("MyProject")
@@ -107,7 +107,6 @@ public class UploadReportActionMediumTest {
     MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
     WsTester.TestRequest request = wsTester.newGetRequest(BatchWs.API_ENDPOINT, UploadReportAction.UPLOAD_REPORT_ACTION);
     request.setParam(UploadReportAction.PARAM_PROJECT, project.key());
-    request.setParam(UploadReportAction.PARAM_FIRST_ANALYSIS, "true");
     request.execute();
 
     // Check that issue authorization index has been created
index 5034e196290c733c7c0e716573b72cb1b58f3906..962af1149af1420d3c48ffacd551730416e68de3 100644 (file)
@@ -66,6 +66,7 @@ public class IssueDaoTest extends AbstractDaoTestCase {
     assertThat(issue.getComponentId()).isEqualTo(401);
     assertThat(issue.getRootComponentId()).isEqualTo(399);
     assertThat(issue.getRuleId()).isEqualTo(500);
+    assertThat(issue.getLanguage()).isEqualTo("java");
     assertThat(issue.getSeverity()).isEqualTo("BLOCKER");
     assertThat(issue.isManualSeverity()).isFalse();
     assertThat(issue.getMessage()).isNull();
index 8312ebf1c9846d65fb37155849e87e459437f132..7e3261f7b624758ff015d560c70ee836f9ddf831 100644 (file)
 package org.sonar.server.issue.index;
 
 import com.google.common.collect.ImmutableList;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.junit.*;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.security.DefaultGroups;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.web.UserRole;
@@ -147,6 +145,26 @@ public class IssueIndexMediumTest {
     assertThat(result.getHits()).hasSize(2);
   }
 
+  @Test
+  public void filter_by_rule() throws Exception {
+    db.issueDao().insert(session, createIssue().setRule(rule));
+
+    tester.get(RuleDao.class).insert(session, RuleTesting.newDto(RuleKey.of("rule", "without issue")));
+    session.commit();
+
+    assertThat(index.search(IssueQuery.builder().rules(newArrayList(rule.getKey())).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().rules(newArrayList(RuleKey.of("rule", "without issue"))).build(), new QueryContext()).getHits()).isEmpty();
+  }
+
+  @Test
+  public void filter_by_language() throws Exception {
+    db.issueDao().insert(session, createIssue().setRule(rule));
+    session.commit();
+
+    assertThat(index.search(IssueQuery.builder().languages(newArrayList(rule.getLanguage())).build(), new QueryContext()).getHits()).hasSize(1);
+    assertThat(index.search(IssueQuery.builder().languages(newArrayList("unknown")).build(), new QueryContext()).getHits()).isEmpty();
+  }
+
   @Test
   public void is_assigned_filter() throws Exception {
     String assignee = "steph";
@@ -236,6 +254,27 @@ public class IssueIndexMediumTest {
     assertThat(result.getHits()).hasSize(500);
   }
 
+  @Test
+  @Ignore("TODO")
+  public void sort_by_assignee() throws Exception {
+    IssueDto issue1 = createIssue().setAssignee("steph");
+    IssueDto issue2 = createIssue().setAssignee("simon");
+    db.issueDao().insert(session, issue1, issue2);
+    session.commit();
+
+    IssueQuery.Builder query = IssueQuery.builder().sort(IssueQuery.SORT_BY_ASSIGNEE).asc(true);
+    Result<Issue> result = index.search(query.build(), new QueryContext());
+    assertThat(result.getHits()).hasSize(2);
+    assertThat(result.getHits().get(0).assignee()).isEqualTo("simon");
+    assertThat(result.getHits().get(1).assignee()).isEqualTo("steph");
+
+    query = IssueQuery.builder().sort(IssueQuery.SORT_BY_ASSIGNEE).asc(false);
+    result = index.search(query.build(), new QueryContext());
+    assertThat(result.getHits()).hasSize(2);
+    assertThat(result.getHits().get(0).assignee()).isEqualTo("steph");
+    assertThat(result.getHits().get(1).assignee()).isEqualTo("simon");
+  }
+
   @Test
   public void authorized_issues_on_groups() throws Exception {
     ComponentDto project1 = new ComponentDto()
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsMediumTest.java
deleted file mode 100644 (file)
index fb8d942..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.ws;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.api.security.DefaultGroups;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.core.permission.PermissionFacade;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.rule.RuleDto;
-import org.sonar.server.component.SnapshotTesting;
-import org.sonar.server.db.DbClient;
-import org.sonar.server.rule.RuleTesting;
-import org.sonar.server.rule.db.RuleDao;
-import org.sonar.server.tester.ServerTester;
-import org.sonar.server.user.MockUserSession;
-import org.sonar.server.ws.WsTester;
-
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class IssuesWsMediumTest {
-
-  @ClassRule
-  public static ServerTester tester = new ServerTester()
-    .setProperty("sonar.issues.use_es_backend", "true");
-
-  IssuesWs ws;
-  DbClient db;
-  DbSession session;
-  WsTester wsTester;
-
-  @Before
-  public void setUp() throws Exception {
-    tester.clearDbAndIndexes();
-    db = tester.get(DbClient.class);
-    ws = tester.get(IssuesWs.class);
-    wsTester = tester.get(WsTester.class);
-    session = db.openSession(false);
-    MockUserSession.set().setLogin("gandalf").setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
-  }
-
-  @After
-  public void after() {
-    session.close();
-  }
-
-  @Test
-  public void define() throws Exception {
-    WebService.Controller controller = wsTester.controller(IssuesWs.API_ENDPOINT);
-
-    assertThat(controller).isNotNull();
-    assertThat(controller.actions()).hasSize(14);
-    assertThat(controller.action(IssuesWs.ADD_COMMENT_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.ASSIGN_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.BULK_CHANGE_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.CHANGELOG_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.CREATE_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.DELETE_COMMENT_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.DO_ACTION_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.DO_TRANSITION_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.EDIT_COMMENT_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.PLAN_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.SET_SEVERITY_ACTION)).isNotNull();
-    assertThat(controller.action(IssuesWs.TRANSITIONS_ACTION)).isNotNull();
-
-    assertThat(controller.action(SearchAction.SEARCH_ACTION)).isNotNull();
-  }
-
-  @Test
-  public void empty_search() throws Exception {
-
-    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
-    WsTester.Result result = request.execute();
-
-    assertThat(result).isNotNull();
-    result.assertJson(this.getClass(), "empty_result.json", false);
-  }
-
-  @Test
-  public void find_single_result() throws Exception {
-
-    RuleDto rule = RuleTesting.newXooX1();
-    tester.get(RuleDao.class).insert(session, rule);
-
-    ComponentDto project = new ComponentDto()
-      .setId(1L)
-      .setKey("MyProject")
-      .setProjectId(1L);
-    db.componentDao().insert(session, project);
-    db.snapshotDao().insert(session, SnapshotTesting.createForComponent(project));
-
-    // project can be seen by anyone
-    tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), DefaultGroups.ANYONE, UserRole.USER, session);
-    db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
-
-    ComponentDto resource = new ComponentDto()
-      .setProjectId(1L)
-      .setKey("MyComponent")
-      .setId(2L);
-    db.componentDao().insert(session, resource);
-    db.snapshotDao().insert(session, SnapshotTesting.createForComponent(resource));
-
-    IssueDto issue = new IssueDto()
-      .setIssueCreationDate(DateUtils.parseDate("2014-09-04"))
-      .setIssueUpdateDate(DateUtils.parseDate("2014-12-04"))
-      .setRule(rule)
-      .setDebt(10L)
-      .setRootComponent(project)
-      .setComponent(resource)
-      .setStatus("OPEN").setResolution("OPEN")
-      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
-      .setSeverity("MAJOR");
-    db.issueDao().insert(session, issue);
-
-    session.commit();
-
-    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
-    request.setParam(SearchAction.PARAM_FACETS, "true");
-    WsTester.Result result = request.execute();
-    assertThat(result).isNotNull();
-
-    // TODO Date assertion is complex du to System2
-    result.assertJson(this.getClass(), "single_result.json", false);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java
new file mode 100644 (file)
index 0000000..5183b72
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.permission.PermissionFacade;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.server.component.SnapshotTesting;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.issue.IssueTesting;
+import org.sonar.server.issue.db.IssueDao;
+import org.sonar.server.issue.filter.IssueFilterParameters;
+import org.sonar.server.rule.RuleTesting;
+import org.sonar.server.rule.db.RuleDao;
+import org.sonar.server.tester.ServerTester;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.ws.WsTester;
+
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class SearchActionMediumTest {
+
+  @ClassRule
+  public static ServerTester tester = new ServerTester()
+    .setProperty("sonar.issues.use_es_backend", "true");
+
+  IssuesWs ws;
+  DbClient db;
+  DbSession session;
+  WsTester wsTester;
+
+  RuleDto rule;
+  ComponentDto project;
+  ComponentDto file;
+
+  @Before
+  public void setUp() throws Exception {
+    tester.clearDbAndIndexes();
+    db = tester.get(DbClient.class);
+    ws = tester.get(IssuesWs.class);
+    wsTester = tester.get(WsTester.class);
+    session = db.openSession(false);
+
+    rule = RuleTesting.newXooX1();
+    tester.get(RuleDao.class).insert(session, rule);
+
+    project = new ComponentDto()
+      .setId(1L)
+      .setKey("MyProject")
+      .setProjectId(1L);
+    db.componentDao().insert(session, project);
+    db.snapshotDao().insert(session, SnapshotTesting.createForComponent(project));
+
+    // project can be seen by anyone
+    tester.get(PermissionFacade.class).insertGroupPermission(project.getId(), DefaultGroups.ANYONE, UserRole.USER, session);
+    db.issueAuthorizationDao().synchronizeAfter(session, new Date(0));
+
+    file = new ComponentDto()
+      .setProjectId(1L)
+      .setKey("MyComponent")
+      .setId(2L);
+    db.componentDao().insert(session, file);
+    db.snapshotDao().insert(session, SnapshotTesting.createForComponent(file));
+
+    session.commit();
+
+    MockUserSession.set().setLogin("gandalf");
+  }
+
+  @After
+  public void after() {
+    session.close();
+  }
+
+  @Test
+  public void empty_search() throws Exception {
+    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+    WsTester.Result result = request.execute();
+
+    assertThat(result).isNotNull();
+    result.assertJson(this.getClass(), "empty_result.json", false);
+  }
+
+  @Test
+  public void find_single_result() throws Exception {
+    IssueDto issue = IssueTesting.newDto(rule, file, project)
+      .setIssueCreationDate(DateUtils.parseDate("2014-09-04"))
+      .setIssueUpdateDate(DateUtils.parseDate("2014-12-04"))
+      .setRule(rule)
+      .setDebt(10L)
+      .setRootComponent(project)
+      .setComponent(file)
+      .setStatus("OPEN").setResolution("OPEN")
+      .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")
+      .setSeverity("MAJOR");
+    db.issueDao().insert(session, issue);
+    session.commit();
+
+    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+    request.setParam(SearchAction.PARAM_FACETS, "true");
+
+    WsTester.Result result = request.execute();
+    // TODO Date assertion is complex du to System2
+    result.assertJson(this.getClass(), "single_result.json", false);
+  }
+
+  @Test
+  public void paging() throws Exception {
+    for (int i=0; i<12; i++) {
+      IssueDto issue = IssueTesting.newDto(rule, file, project);
+      tester.get(IssueDao.class).insert(session, issue);
+    }
+    session.commit();
+
+    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+    request.setParam(SearchAction.PARAM_PAGE, "2");
+    request.setParam(SearchAction.PARAM_PAGE_SIZE, "9");
+
+    WsTester.Result result = request.execute();
+    result.assertJson(this.getClass(), "paging.json", false);
+  }
+
+  @Test
+  public void paging_with_deprecated_params() throws Exception {
+    for (int i=0; i<12; i++) {
+      IssueDto issue = IssueTesting.newDto(rule, file, project);
+      tester.get(IssueDao.class).insert(session, issue);
+    }
+    session.commit();
+
+    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+    request.setParam(IssueFilterParameters.PAGE_INDEX, "2");
+    request.setParam(IssueFilterParameters.PAGE_SIZE, "9");
+
+    WsTester.Result result = request.execute();
+    result.assertJson(this.getClass(), "paging.json", false);
+  }
+
+  @Test
+  public void default_page_size_is_100() throws Exception {
+    WsTester.TestRequest request = wsTester.newGetRequest(IssuesWs.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+
+    WsTester.Result result = request.execute();
+    result.assertJson(this.getClass(), "default_page_size_is_100.json", false);
+  }
+
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/empty_result.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/empty_result.json
deleted file mode 100644 (file)
index 60269eb..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-{"maxResultsReached": false, "paging": {
-  "pageIndex": 1,
-  "pageSize": 10,
-  "total": 0,
-  "fTotal": "0",
-  "pages": 0
-}, "issues": [], "components": [], "projects": []}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/single_result.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/IssuesWsMediumTest/single_result.json
deleted file mode 100644 (file)
index 4fa5dc2..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-{"maxResultsReached": false, "paging": {
-  "pageIndex": 1,
-  "pageSize": 10,
-  "total": 1,
-  "fTotal": "1",
-  "pages": 1
-}, "issues": [
-  {
-    "key": "82fd47d4-b650-4037-80bc-7b112bd4eac2",
-    "component": "MyComponent",
-    "project": "MyProject",
-    "rule": "xoo:x1",
-    "status": "OPEN",
-    "resolution": "OPEN",
-    "severity": "MAJOR",
-    "debt": "10min",
-    "fUpdateAge": "less than a minute"
-  }
-], "components": [
-  {
-    "key": "MyComponent",
-    "id": 2
-  }
-], "projects": [
-  {
-    "key": "MyProject",
-    "id": 1
-  }
-], "facets": [
-  {
-    "property": "status",
-    "values": [
-      {
-        "val": "OPEN",
-        "count": 1
-      }
-    ]
-  },
-  {
-    "property": "severity",
-    "values": [
-      {
-        "val": "MAJOR",
-        "count": 1
-      }
-    ]
-  },
-  {
-    "property": "resolution",
-    "values": [
-      {
-        "val": "OPEN",
-        "count": 1
-      }
-    ]
-  }
-]}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/default_page_size_is_100.json
new file mode 100644 (file)
index 0000000..0fc07a0
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "total": 0,
+  "p": 1,
+  "ps": 100,
+  "maxResultsReached": false,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 0,
+    "fTotal": "0",
+    "pages": 0
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/empty_result.json
new file mode 100644 (file)
index 0000000..4d91f3e
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "total": 0,
+  "p": 1,
+  "ps": 100,
+  "maxResultsReached": false,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 0,
+    "fTotal": "0",
+    "pages": 0
+  },
+  "issues": [],
+  "components": [],
+  "projects": []
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/paging.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/paging.json
new file mode 100644 (file)
index 0000000..ec4dd63
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "total": 12,
+  "p": 2,
+  "ps": 9,
+  "maxResultsReached": false,
+  "paging": {
+    "pageIndex": 2,
+    "pageSize": 9,
+    "total": 12,
+    "fTotal": "12",
+    "pages": 2
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/single_result.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/single_result.json
new file mode 100644 (file)
index 0000000..28202fd
--- /dev/null
@@ -0,0 +1,63 @@
+{
+  "maxResultsReached": false,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 1,
+    "fTotal": "1",
+    "pages": 1
+  },
+  "issues": [
+    {
+      "key": "82fd47d4-b650-4037-80bc-7b112bd4eac2",
+      "component": "MyComponent",
+      "project": "MyProject",
+      "rule": "xoo:x1",
+      "status": "OPEN",
+      "resolution": "OPEN",
+      "severity": "MAJOR",
+      "debt": "10min",
+      "fUpdateAge": "less than a minute"
+    }
+  ],
+  "components": [
+    {
+      "key": "MyComponent",
+      "id": 2
+    }
+  ],
+  "projects": [
+    {
+      "key": "MyProject",
+      "id": 1
+    }
+  ],
+  "facets": [
+    {
+      "property": "status",
+      "values": [
+        {
+          "val": "OPEN",
+          "count": 1
+        }
+      ]
+    },
+    {
+      "property": "severity",
+      "values": [
+        {
+          "val": "MAJOR",
+          "count": 1
+        }
+      ]
+    },
+    {
+      "property": "resolution",
+      "values": [
+        {
+          "val": "OPEN",
+          "count": 1
+        }
+      ]
+    }
+  ]}
index fa67f6872a9589ad1d2057895fa172ac8920cf85..15092940d6829f8fbe2368de1b6bfa01e205117f 100644 (file)
@@ -78,6 +78,7 @@ public final class IssueDto extends Dto<String> implements Serializable {
   // joins
   private String ruleKey;
   private String ruleRepo;
+  private String language;
   private String componentKey;
   private String rootComponentKey;
 
@@ -153,6 +154,7 @@ public final class IssueDto extends Dto<String> implements Serializable {
     this.ruleId = rule.getId();
     this.ruleKey = rule.getRuleKey();
     this.ruleRepo = rule.getRepositoryKey();
+    this.language = rule.getLanguage();
     return this;
   }
 
@@ -353,6 +355,10 @@ public final class IssueDto extends Dto<String> implements Serializable {
     return RuleKey.of(ruleRepo, ruleKey);
   }
 
+  public String getLanguage(){
+    return language;
+  }
+
   public String getComponentKey() {
     return componentKey;
   }
@@ -382,6 +388,16 @@ public final class IssueDto extends Dto<String> implements Serializable {
     return this;
   }
 
+  /**
+   * Should only be used to persist in E/S
+   *
+   * Please use {@link #setRule(org.sonar.core.rule.RuleDto)} instead
+   */
+  public IssueDto setLanguage(String language) {
+    this.language = language;
+    return this;
+  }
+
   /**
    * Should only be used to persist in E/S
    *
@@ -485,6 +501,7 @@ public final class IssueDto extends Dto<String> implements Serializable {
     issue.setProjectKey(rootComponentKey);
     issue.setManualSeverity(manualSeverity);
     issue.setRuleKey(getRuleKey());
+    issue.setLanguage(language);
     issue.setActionPlanKey(actionPlanKey);
     issue.setAuthorLogin(authorLogin);
     issue.setNew(false);
index 76fb97eaadf3bbf17de4af23d561a28d131bdc76..cd9e5cfa79da938d13a9254f667bae1f0ce7d403 100644 (file)
@@ -31,6 +31,7 @@
     i.updated_at as updatedAt,
     r.plugin_rule_key as ruleKey,
     r.plugin_name as ruleRepo,
+    r.language as language,
     p.kee as componentKey,
     root.kee as rootComponentKey
   </sql>
index ddda3b57f718240645289ca1b7d50ee3e7de0d17..791c7125556c99ea857279be7b29d6560d414152 100644 (file)
@@ -59,6 +59,7 @@ public class IssueDtoTest {
       .setKee("100")
       .setRuleId(1)
       .setRuleKey("squid", "AvoidCycle")
+      .setLanguage("xoo")
       .setComponentKey("org.sonar.sample:Sample")
       .setRootComponentKey("org.sonar.sample")
       .setComponentId(1l)
@@ -82,6 +83,7 @@ public class IssueDtoTest {
     DefaultIssue issue = dto.toDefaultIssue();
     assertThat(issue.key()).isEqualTo("100");
     assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
+    assertThat(issue.language()).isEqualTo("xoo");
     assertThat(issue.componentKey()).isEqualTo("org.sonar.sample:Sample");
     assertThat(issue.componentId()).isEqualTo(1L);
     assertThat(issue.projectKey()).isEqualTo("org.sonar.sample");
index b3907d233def83bb183db4d9e00c08aa56baca24..44c8b456fd394bc5ecb7a086a20fbc6e0b2d7bea 100644 (file)
@@ -83,6 +83,8 @@ public interface Issue extends Serializable {
 
   RuleKey ruleKey();
 
+  String language();
+
   /**
    * See constants in {@link org.sonar.api.rule.Severity}.
    */
index 51a5abfd174c72226549361e6a64b46da21b641a..a6c528dc2a6c8b8d52030c721b4f04e8a427909f 100644 (file)
@@ -39,11 +39,7 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
 import java.io.Serializable;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static com.google.common.collect.Lists.newArrayList;
 
@@ -59,6 +55,7 @@ public class DefaultIssue implements Issue {
   private Long componentId;
   private String projectKey;
   private RuleKey ruleKey;
+  private String language;
   private String severity;
   private boolean manualSeverity = false;
   private String message;
@@ -159,6 +156,15 @@ public class DefaultIssue implements Issue {
     return this;
   }
 
+  public String language() {
+    return language;
+  }
+
+  public DefaultIssue setLanguage(String l) {
+    this.language = l;
+    return this;
+  }
+
   public String severity() {
     return severity;
   }