aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2013-04-17 18:29:35 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2013-04-17 18:36:56 +0200
commit7660b174b186527b5df5354295886c42dec2109d (patch)
tree0ed5f14607232f9161741a486e5d67ca48440e93
parent398c82a5810e446a22b31d204a87ad7fd806a068 (diff)
downloadsonarqube-7660b174b186527b5df5354295886c42dec2109d.tar.gz
sonarqube-7660b174b186527b5df5354295886c42dec2109d.zip
SONAR-3755 support the parameter "rules" in /api/issues/search
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java36
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java2
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml6
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java10
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/issue/IssueDaoTest/select.xml4
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueChange.java146
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java93
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java22
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueChangeTest.java41
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueQueryTest.java29
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java40
-rw-r--r--sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java58
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb4
-rw-r--r--sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java29
-rw-r--r--sonar-ws-client/src/main/java/org/sonar/wsclient/services/IssueQuery.java36
-rw-r--r--sonar-ws-client/src/test/java/org/sonar/wsclient/services/IssueQueryTest.java20
18 files changed, 414 insertions, 168 deletions
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java
index f71b14be13d..5f4bd71fdb8 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssuesWorkflowDecoratorTest.java
@@ -25,8 +25,8 @@ import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.api.issue.Issue;
-import org.sonar.api.resources.JavaFile;
import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
@@ -49,20 +49,15 @@ import static org.mockito.Mockito.*;
public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
- private IssuesWorkflowDecorator decorator;
- private ModuleIssues moduleIssues;
- private InitialOpenIssuesStack initialOpenIssuesStack;
- private IssueTracking issueTracking;
- private RuleFinder ruleFinder;
+ IssuesWorkflowDecorator decorator;
+ ModuleIssues moduleIssues = mock(ModuleIssues.class);
+ InitialOpenIssuesStack initialOpenIssuesStack = mock(InitialOpenIssuesStack.class);
+ IssueTracking issueTracking = mock(IssueTracking.class);
+ RuleFinder ruleFinder = mock(RuleFinder.class);
@Before
public void init() {
- moduleIssues = mock(ModuleIssues.class);
- initialOpenIssuesStack = mock(InitialOpenIssuesStack.class);
- issueTracking = mock(IssueTracking.class);
- ruleFinder = mock(RuleFinder.class);
- when(ruleFinder.findById(anyInt())).thenReturn(Rule.create());
-
+ when(ruleFinder.findById(anyInt())).thenReturn(Rule.create().setRepositoryKey("squid").setKey("AvoidCycle"));
decorator = new IssuesWorkflowDecorator(moduleIssues, initialOpenIssuesStack, issueTracking, ruleFinder);
}
@@ -86,8 +81,7 @@ public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(
new IssueDto().setUuid("100").setRuleId(10)));
- Resource resource = new JavaFile("key");
- decorator.decorate(resource, null);
+ decorator.decorate(mock(Resource.class), null);
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(moduleIssues).addOrUpdate(argument.capture());
@@ -101,8 +95,7 @@ public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(
new IssueDto().setUuid("100").setRuleId(1).setManualIssue(true).setStatus(Issue.STATUS_RESOLVED)));
- Resource resource = new JavaFile("key");
- decorator.decorate(resource, null);
+ decorator.decorate(mock(Resource.class), null);
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(moduleIssues).addOrUpdate(argument.capture());
@@ -117,8 +110,7 @@ public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(
new IssueDto().setUuid("100").setRuleId(1).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FIXED)));
- Resource resource = new JavaFile("key");
- decorator.decorate(resource, null);
+ decorator.decorate(mock(Resource.class), null);
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(moduleIssues, times(2)).addOrUpdate(argument.capture());
@@ -138,8 +130,7 @@ public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(
new IssueDto().setUuid("100").setRuleId(1).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE)));
- Resource resource = new JavaFile("key");
- decorator.decorate(resource, null);
+ decorator.decorate(mock(Resource.class), null);
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
verify(moduleIssues, times(2)).addOrUpdate(argument.capture());
@@ -153,13 +144,14 @@ public class IssuesWorkflowDecoratorTest extends AbstractDaoTestCase {
}
@Test
- @Ignore // TODO
+ @Ignore
public void should_close_remaining_open_issue_on_root_project() {
when(moduleIssues.issues(anyString())).thenReturn(Collections.<Issue>emptyList());
when(initialOpenIssuesStack.selectAndRemove(anyInt())).thenReturn(newArrayList(
new IssueDto().setUuid("100").setRuleId(1)));
- Resource resource = new Project("key");
+ Resource resource = mock(Resource.class);
+ when(resource.getQualifier()).thenReturn(Qualifiers.PROJECT);
decorator.decorate(resource, null);
ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
index fb15783108d..cd1bb9f78a0 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
@@ -130,7 +130,7 @@ public class DefaultIssue implements Issue, Serializable {
}
public DefaultIssue setLine(@Nullable Integer l) {
- Preconditions.checkArgument(l == null || l > 0, "Line must be greater than zero (got " + l + ")");
+ Preconditions.checkArgument(l == null || l > 0, "Line must be null or greater than zero (got " + l + ")");
this.line = l;
return this;
}
diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
index 3e527c0ef8e..b731c4bf3f2 100644
--- a/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
+++ b/sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
@@ -107,7 +107,7 @@
<if test="components != null and components.size() > 0">
, projects project_component
</if>
- <if test="ruleRepository != null and rule != null">
+ <if test="rules != null and rules.size() > 0">
, rules r
</if>
<where>
@@ -169,9 +169,9 @@
<foreach item="assigneeLogin" index="index" collection="assigneeLogins" open="(" separator="," close=")">#{assigneeLogin}
</foreach>
</if>
- <if test="ruleRepository != null and rule != null">
- and r.plugin_name = #{ruleRepository} and r.plugin_rule_key = #{rule}
+ <if test="rules != null and rules.size() > 0">
and i.rule_id = r.id
+ and (<foreach item="rule" index="index" collection="rules" open="(" separator=" or " close=")">r.plugin_name=#{rule.repository} and r.plugin_rule_key=#{rule.rule}</foreach>)
</if>
<if test="createdAfter">
and i.created_at &gt; #{createdAfter}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
index ad16cffd099..03b296ac13e 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueDaoTest.java
@@ -23,6 +23,7 @@ package org.sonar.core.issue;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.DateUtils;
import org.sonar.core.persistence.AbstractDaoTestCase;
@@ -67,7 +68,7 @@ public class IssueDaoTest extends AbstractDaoTestCase {
dao.insert(issueDto);
- checkTables("insert", new String[] {"id", "created_at", "updated_at", "closed_at"}, "issues");
+ checkTables("insert", new String[]{"id", "created_at", "updated_at", "closed_at"}, "issues");
}
@Test
@@ -158,11 +159,14 @@ public class IssueDaoTest extends AbstractDaoTestCase {
}
@Test
- public void should_find_issue_by_rule() {
+ public void should_find_issue_by_rules() {
setupData("select");
- IssueQuery issueQuery = IssueQuery.builder().rule("rule").ruleRepository("repo").build();
+ IssueQuery issueQuery = IssueQuery.builder().rules(newArrayList(RuleKey.of("squid", "AvoidCycle"))).build();
assertThat(dao.select(issueQuery)).hasSize(4);
+
+ issueQuery = IssueQuery.builder().rules(newArrayList(RuleKey.of("squid", "Other"))).build();
+ assertThat(dao.select(issueQuery)).isEmpty();
}
@Test
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/IssueDaoTest/select.xml b/sonar-core/src/test/resources/org/sonar/core/issue/IssueDaoTest/select.xml
index 73899ccbaf8..31fb7d91297 100644
--- a/sonar-core/src/test/resources/org/sonar/core/issue/IssueDaoTest/select.xml
+++ b/sonar-core/src/test/resources/org/sonar/core/issue/IssueDaoTest/select.xml
@@ -160,8 +160,8 @@
<rules
id="500"
- plugin_name="repo"
- plugin_rule_key="rule"
+ plugin_name="squid"
+ plugin_rule_key="AvoidCycle"
/>
</dataset>
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueChange.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueChange.java
new file mode 100644
index 00000000000..1587ca25453
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueChange.java
@@ -0,0 +1,146 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.issue;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class IssueChange {
+ private String severity = null;
+ private String comment = null;
+ private Boolean manualSeverity = null;
+ private String message = null;
+ private boolean lineChanged = false;
+ private Integer line = null;
+ private boolean costChanged = false;
+ private Double cost = null;
+ private String resolution = null;
+ private boolean assigneeLoginChanged = false;
+ private String assigneeLogin = null;
+ private Map<String, String> attributes = null;
+
+ private IssueChange() {
+ }
+
+ public static IssueChange create() {
+ return new IssueChange();
+ }
+
+ public boolean hasChanges() {
+ return severity != null || comment != null || manualSeverity != null || message != null ||
+ lineChanged || costChanged || resolution != null || assigneeLoginChanged || attributes != null;
+ }
+
+ public IssueChange setSeverity(String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public IssueChange setComment(String comment) {
+ this.comment = comment;
+ return this;
+ }
+
+ public IssueChange setManualSeverity(boolean b) {
+ this.manualSeverity = b;
+ return this;
+ }
+
+ public IssueChange setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public IssueChange setLine(@Nullable Integer line) {
+ this.lineChanged = true;
+ this.line = line;
+ return this;
+ }
+
+ public IssueChange setCost(@Nullable Double cost) {
+ this.costChanged = true;
+ this.cost = cost;
+ return this;
+ }
+
+ public IssueChange setResolution(String resolution) {
+ this.resolution = resolution;
+ return this;
+ }
+
+ public IssueChange setAssigneeLogin(@Nullable String assigneeLogin) {
+ this.assigneeLoginChanged = true;
+ this.assigneeLogin = assigneeLogin;
+ return this;
+ }
+
+ public IssueChange setAttribute(String key, @Nullable String value) {
+ if (attributes == null && value != null) {
+ attributes = new LinkedHashMap<String, String>();
+ }
+ if (value != null) {
+ attributes.put(key, value);
+ } else if (attributes != null) {
+ attributes.remove(key);
+ }
+ return this;
+ }
+
+ public String severity() {
+ return severity;
+ }
+
+ public String comment() {
+ return comment;
+ }
+
+ public Boolean manualSeverity() {
+ return manualSeverity;
+ }
+
+ public String message() {
+ return message;
+ }
+
+ public Integer line() {
+ return line;
+ }
+
+ public Double cost() {
+ return cost;
+ }
+
+ public String resolution() {
+ return resolution;
+ }
+
+ public String assigneeLogin() {
+ return assigneeLogin;
+ }
+
+ public Map<String, String> attributes() {
+ return attributes == null ? Collections.<String, String>emptyMap() : new LinkedHashMap<String, String>(attributes);
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java
index ab6ec98a9cd..9ab187dab22 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java
@@ -22,9 +22,10 @@ package org.sonar.api.issue;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.sonar.api.rule.RuleKey;
import java.util.Date;
-import java.util.List;
+import java.util.Collection;
/**
* TODO add javadoc
@@ -33,16 +34,15 @@ import java.util.List;
*/
public class IssueQuery {
- private final List<String> keys;
- private final List<String> severities;
- private final List<String> statuses;
- private final List<String> resolutions;
- private final List<String> components;
- private final List<String> componentRoots;
- private final String ruleRepository;
- private final String rule;
- private final List<String> userLogins;
- private final List<String> assigneeLogins;
+ private final Collection<String> keys;
+ private final Collection<String> severities;
+ private final Collection<String> statuses;
+ private final Collection<String> resolutions;
+ private final Collection<String> components;
+ private final Collection<String> componentRoots;
+ private final Collection<RuleKey> rules;
+ private final Collection<String> userLogins;
+ private final Collection<String> assigneeLogins;
private final Date createdAfter;
private final Date createdBefore;
private final int limit, offset;
@@ -54,8 +54,7 @@ public class IssueQuery {
this.resolutions = builder.resolutions;
this.components = builder.components;
this.componentRoots = builder.componentRoots;
- this.ruleRepository = builder.ruleRepository;
- this.rule = builder.rule;
+ this.rules = builder.rules;
this.userLogins = builder.userLogins;
this.assigneeLogins = builder.assigneeLogins;
this.createdAfter = builder.createdAfter;
@@ -64,43 +63,39 @@ public class IssueQuery {
this.offset = builder.offset;
}
- public List<String> keys() {
+ public Collection<String> keys() {
return keys;
}
- public List<String> severities() {
+ public Collection<String> severities() {
return severities;
}
- public List<String> statuses() {
+ public Collection<String> statuses() {
return statuses;
}
- public List<String> resolutions() {
+ public Collection<String> resolutions() {
return resolutions;
}
- public List<String> components() {
+ public Collection<String> components() {
return components;
}
- public List<String> componentRoots() {
+ public Collection<String> componentRoots() {
return componentRoots;
}
- public String ruleRepository() {
- return ruleRepository;
+ public Collection<RuleKey> rules() {
+ return rules;
}
- public String rule() {
- return rule;
- }
-
- public List<String> userLogins() {
+ public Collection<String> userLogins() {
return userLogins;
}
- public List<String> assigneeLogins() {
+ public Collection<String> assigneeLogins() {
return assigneeLogins;
}
@@ -138,16 +133,15 @@ public class IssueQuery {
private static final int MAX_LIMIT = 5000;
private static final int DEFAULT_OFFSET = 0;
- private List<String> keys;
- private List<String> severities;
- private List<String> statuses;
- private List<String> resolutions;
- private List<String> components;
- private List<String> componentRoots;
- private String ruleRepository;
- private String rule;
- private List<String> userLogins;
- private List<String> assigneeLogins;
+ private Collection<String> keys;
+ private Collection<String> severities;
+ private Collection<String> statuses;
+ private Collection<String> resolutions;
+ private Collection<String> components;
+ private Collection<String> componentRoots;
+ private Collection<RuleKey> rules;
+ private Collection<String> userLogins;
+ private Collection<String> assigneeLogins;
private Date createdAfter;
private Date createdBefore;
private int limit = DEFAULT_LIMIT;
@@ -156,52 +150,47 @@ public class IssueQuery {
private Builder() {
}
- public Builder keys(List<String> l) {
+ public Builder keys(Collection<String> l) {
this.keys = l;
return this;
}
- public Builder severities(List<String> l) {
+ public Builder severities(Collection<String> l) {
this.severities = l;
return this;
}
- public Builder statuses(List<String> l) {
+ public Builder statuses(Collection<String> l) {
this.statuses = l;
return this;
}
- public Builder resolutions(List<String> l) {
+ public Builder resolutions(Collection<String> l) {
this.resolutions = l;
return this;
}
- public Builder components(List<String> l) {
+ public Builder components(Collection<String> l) {
this.components = l;
return this;
}
- public Builder componentRoots(List<String> l) {
+ public Builder componentRoots(Collection<String> l) {
this.componentRoots = l;
return this;
}
- public Builder ruleRepository(String ruleRepository) {
- this.ruleRepository = ruleRepository;
- return this;
- }
-
- public Builder rule(String rule) {
- this.rule = rule;
+ public Builder rules(Collection<RuleKey> rules) {
+ this.rules = rules;
return this;
}
- public Builder userLogins(List<String> l) {
+ public Builder userLogins(Collection<String> l) {
this.userLogins = l;
return this;
}
- public Builder assigneeLogins(List<String> l) {
+ public Builder assigneeLogins(Collection<String> l) {
this.assigneeLogins = l;
return this;
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java
index af3a0a0bffc..6ec537a8399 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java
@@ -24,7 +24,7 @@ import org.sonar.api.ServerComponent;
import java.util.Map;
/**
- * Facade for JRuby on Rails extensions.
+ * Facade for JRuby on Rails extensions to request issues.
* <p>
* Reference from Ruby code : <code>Api.issues</code>
* </p>
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
index 6227d717646..a286b0f5750 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
@@ -20,9 +20,14 @@
package org.sonar.api.rule;
import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
import java.io.Serializable;
+/**
+ * Key of a rule. Unique among all the rule repositories.
+ * @since 3.6
+ */
public class RuleKey implements Serializable {
private final String repository, rule;
@@ -31,20 +36,35 @@ public class RuleKey implements Serializable {
this.rule = rule;
}
+ /**
+ * Create a key. Parameters are NOT null.
+ */
public static RuleKey of(String repository, String rule) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(repository), "Repository must be set");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(rule), "Rule must be set");
return new RuleKey(repository, rule);
}
+ /**
+ * Create a key from a string representation (see {@link #toString()}. An {@link IllegalArgumentException} is raised
+ * if the format is not valid.
+ */
public static RuleKey parse(String s) {
String[] split = s.split(":");
Preconditions.checkArgument(split.length == 2, "Bad format of rule key: " + s);
return RuleKey.of(split[0], split[1]);
}
+ /**
+ * Never null
+ */
public String repository() {
return repository;
}
+ /**
+ * Never null
+ */
public String rule() {
return rule;
}
@@ -75,7 +95,7 @@ public class RuleKey implements Serializable {
}
/**
- * Do not change this format because it's used by customers of the API (rails, ...)
+ * Format is "repository:rule", for example "squid:AvoidCycle"
*/
@Override
public String toString() {
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueChangeTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueChangeTest.java
new file mode 100644
index 00000000000..bd51b76127a
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueChangeTest.java
@@ -0,0 +1,41 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.issue;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class IssueChangeTest {
+ @Test
+ public void should_not_have_changes_by_default() throws Exception {
+ IssueChange change = IssueChange.create();
+ assertThat(change.hasChanges()).isFalse();
+ assertThat(change.severity()).isNull();
+ assertThat(change.cost()).isNull();
+ assertThat(change.assigneeLogin()).isNull();
+ assertThat(change.line()).isNull();
+ assertThat(change.comment()).isNull();
+ assertThat(change.message()).isNull();
+ assertThat(change.resolution()).isNull();
+ assertThat(change.manualSeverity()).isNull();
+ assertThat(change.attributes()).isEmpty();
+ }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueQueryTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueQueryTest.java
index 5b3c4491ee4..324d2d78c3c 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueQueryTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/issue/IssueQueryTest.java
@@ -21,6 +21,7 @@ package org.sonar.api.issue;
import com.google.common.collect.Lists;
import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import java.util.Date;
@@ -36,27 +37,25 @@ public class IssueQueryTest {
.severities(Lists.newArrayList(Severity.BLOCKER))
.statuses(Lists.newArrayList(Issue.STATUS_RESOLVED))
.resolutions(Lists.newArrayList(Issue.RESOLUTION_FALSE_POSITIVE))
- .components(Lists.newArrayList("components"))
- .componentRoots(Lists.newArrayList("componentRoots"))
- .ruleRepository("ruleRepository")
- .rule("rule")
- .userLogins(Lists.newArrayList("user"))
+ .components(Lists.newArrayList("org/struts/Action.java"))
+ .componentRoots(Lists.newArrayList("org.struts:core"))
+ .rules(Lists.newArrayList(RuleKey.of("squid", "AvoidCycle")))
+ .userLogins(Lists.newArrayList("crunky"))
.assigneeLogins(Lists.newArrayList("gargantua"))
.createdAfter(new Date())
.createdBefore(new Date())
.limit(125)
.offset(33)
.build();
- assertThat(query.keys()).containsExactly("ABCDE");
- assertThat(query.severities()).containsExactly(Severity.BLOCKER);
- assertThat(query.statuses()).containsExactly(Issue.STATUS_RESOLVED);
- assertThat(query.resolutions()).containsExactly(Issue.RESOLUTION_FALSE_POSITIVE);
- assertThat(query.components()).containsExactly("components");
- assertThat(query.componentRoots()).containsExactly("componentRoots");
- assertThat(query.userLogins()).containsExactly("user");
- assertThat(query.assigneeLogins()).containsExactly("gargantua");
- assertThat(query.ruleRepository()).isEqualTo("ruleRepository");
- assertThat(query.rule()).isEqualTo("rule");
+ assertThat(query.keys()).containsOnly("ABCDE");
+ assertThat(query.severities()).containsOnly(Severity.BLOCKER);
+ assertThat(query.statuses()).containsOnly(Issue.STATUS_RESOLVED);
+ assertThat(query.resolutions()).containsOnly(Issue.RESOLUTION_FALSE_POSITIVE);
+ assertThat(query.components()).containsOnly("org/struts/Action.java");
+ assertThat(query.componentRoots()).containsOnly("org.struts:core");
+ assertThat(query.userLogins()).containsOnly("crunky");
+ assertThat(query.assigneeLogins()).containsOnly("gargantua");
+ assertThat(query.rules()).containsOnly(RuleKey.of("squid", "AvoidCycle"));
assertThat(query.createdAfter()).isNotNull();
assertThat(query.createdBefore()).isNotNull();
assertThat(query.limit()).isEqualTo(125);
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java
index d4411fb860f..aee99313bbc 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleKeyTest.java
@@ -33,6 +33,46 @@ public class RuleKeyTest {
}
@Test
+ public void repository_must_not_be_null() throws Exception {
+ try {
+ RuleKey.of(null, "NullDeref");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Repository must be set");
+ }
+ }
+
+ @Test
+ public void repository_must_not_be_empty() throws Exception {
+ try {
+ RuleKey.of("", "NullDeref");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Repository must be set");
+ }
+ }
+
+ @Test
+ public void rule_must_not_be_null() throws Exception {
+ try {
+ RuleKey.of("squid", null);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Rule must be set");
+ }
+ }
+
+ @Test
+ public void rule_must_not_be_empty() throws Exception {
+ try {
+ RuleKey.of("squid", "");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Rule must be set");
+ }
+ }
+
+ @Test
public void should_encode_and_decode_string() throws Exception {
RuleKey key = RuleKey.of("squid", "NullDeref");
String serialized = key.toString();
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java b/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java
index 13874b798d0..07887c8073e 100644
--- a/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java
+++ b/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java
@@ -19,14 +19,19 @@
*/
package org.sonar.server.issue;
+import com.google.common.base.Function;
import com.google.common.base.Splitter;
+import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import org.sonar.api.issue.IssueFinder;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.issue.JRubyIssues;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.DateUtils;
import org.sonar.server.ui.JRubyFacades;
+import javax.annotation.Nullable;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -51,16 +56,15 @@ public class DefaultJRubyIssues implements JRubyIssues {
IssueQuery newQuery(Map<String, Object> props) {
IssueQuery.Builder builder = IssueQuery.builder();
- builder.keys(toStringList(props.get("keys")));
- builder.severities(toStringList(props.get("severities")));
- builder.statuses(toStringList(props.get("statuses")));
- builder.resolutions(toStringList(props.get("resolutions")));
- builder.components(toStringList(props.get("components")));
- builder.componentRoots(toStringList(props.get("componentRoots")));
- builder.rule(toString(props.get("rule")));
- builder.ruleRepository(toString(props.get("ruleRepository")));
- builder.userLogins(toStringList(props.get("userLogins")));
- builder.assigneeLogins(toStringList(props.get("assigneeLogins")));
+ builder.keys(toStrings(props.get("keys")));
+ builder.severities(toStrings(props.get("severities")));
+ builder.statuses(toStrings(props.get("statuses")));
+ builder.resolutions(toStrings(props.get("resolutions")));
+ builder.components(toStrings(props.get("components")));
+ builder.componentRoots(toStrings(props.get("componentRoots")));
+ builder.rules(toRules(props.get("rules")));
+ builder.userLogins(toStrings(props.get("userLogins")));
+ builder.assigneeLogins(toStrings(props.get("assigneeLogins")));
builder.createdAfter(toDate(props.get("createdAfter")));
builder.createdBefore(toDate(props.get("createdBefore")));
builder.limit(toInteger(props.get("limit")));
@@ -68,7 +72,30 @@ public class DefaultJRubyIssues implements JRubyIssues {
return builder.build();
}
- List<String> toStringList(Object o) {
+ @SuppressWarnings("unchecked")
+ static Collection<RuleKey> toRules(Object o) {
+ Collection<RuleKey> result = null;
+ if (o != null) {
+ if (o instanceof List) {
+ // assume that it contains only strings
+ result = stringsToRules((List<String>) o);
+ } else if (o instanceof String) {
+ result = stringsToRules(Lists.newArrayList(Splitter.on(',').omitEmptyStrings().split((String) o)));
+ }
+ }
+ return result;
+ }
+
+ private static Collection<RuleKey> stringsToRules(Collection<String> o) {
+ return Collections2.transform(o, new Function<String, RuleKey>() {
+ @Override
+ public RuleKey apply(@Nullable String s) {
+ return s != null ? RuleKey.parse(s) : null;
+ }
+ });
+ }
+
+ static List<String> toStrings(Object o) {
List<String> result = null;
if (o != null) {
if (o instanceof List) {
@@ -91,14 +118,7 @@ public class DefaultJRubyIssues implements JRubyIssues {
return null;
}
- String toString(Object o) {
- if (o instanceof String) {
- return ((String) o);
- }
- return null;
- }
-
- Date toDate(Object o){
+ Date toDate(Object o) {
if (o instanceof Date) {
return (Date) o;
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
index 1a162aa4550..0940bc021e3 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
@@ -41,8 +41,8 @@ class Api::IssuesController < Api::ApiController
json = {
:key => issue.key,
:component => issue.componentKey,
- :ruleRepository => issue.ruleRepositoryKey,
- :rule => issue.ruleKey,
+ :ruleRepository => issue.ruleKey.repository,
+ :rule => issue.ruleKey.rule
}
json[:severity] = issue.severity if issue.severity
json[:title] = issue.title if issue.title
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb
index de2809a8f0e..32234197637 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb
@@ -87,10 +87,10 @@
<%= h(issue.message) %>
</td>
<td>
- <%= h issue.component_key -%>
+ <%= h issue.componentKey -%>
</td>
<td>
- <%= issue.rule_repository_key + ":" + issue.rule_key %>
+ <%= issue.ruleKey %>
</td>
<td>
<%= issue.line -%>
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java b/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java
index 7eefbe14d40..ad905028595 100644
--- a/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java
@@ -25,12 +25,14 @@ import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.sonar.api.issue.IssueFinder;
import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.DateUtils;
import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
+import static java.util.Arrays.asList;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
@@ -65,22 +67,20 @@ public class DefaultJRubyIssuesTest {
map.put("assigneeLogins", newArrayList("joanna"));
map.put("createdAfter", "2013-04-16T09:08:24+0200");
map.put("createdBefore", "2013-04-17T09:08:24+0200");
- map.put("rule", "rule");
- map.put("ruleRepository", "ruleRepository");
+ map.put("rules", "squid:AvoidCycle,findbugs:NullReference");
map.put("limit", 10);
map.put("offset", 50);
IssueQuery query = new DefaultJRubyIssues(finder).newQuery(map);
assertThat(query.keys()).containsOnly("ABCDE1234");
- assertThat(query.severities()).containsExactly("MAJOR", "MINOR");
+ assertThat(query.severities()).containsOnly("MAJOR", "MINOR");
assertThat(query.statuses()).containsOnly("CLOSED");
assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
assertThat(query.components()).containsOnly("org.apache");
assertThat(query.componentRoots()).containsOnly("org.sonar");
assertThat(query.userLogins()).containsOnly("marilyn");
assertThat(query.assigneeLogins()).containsOnly("joanna");
- assertThat(query.rule()).isEqualTo("rule");
- assertThat(query.ruleRepository()).isEqualTo("ruleRepository");
+ assertThat(query.rules()).hasSize(2);
assertThat(query.createdAfter()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
assertThat(query.limit()).isEqualTo(10);
@@ -88,6 +88,25 @@ public class DefaultJRubyIssuesTest {
}
@Test
+ public void should_parse_list_of_rules() {
+ assertThat(DefaultJRubyIssues.toRules(null)).isNull();
+ assertThat(DefaultJRubyIssues.toRules("")).isEmpty();
+ assertThat(DefaultJRubyIssues.toRules("squid:AvoidCycle")).containsOnly(RuleKey.of("squid", "AvoidCycle"));
+ assertThat(DefaultJRubyIssues.toRules("squid:AvoidCycle,findbugs:NullRef")).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
+ assertThat(DefaultJRubyIssues.toRules(asList("squid:AvoidCycle", "findbugs:NullRef"))).containsOnly(RuleKey.of("squid", "AvoidCycle"), RuleKey.of("findbugs", "NullRef"));
+ }
+
+ @Test
+ public void should_parse_list_of_strings() {
+ assertThat(DefaultJRubyIssues.toStrings(null)).isNull();
+ assertThat(DefaultJRubyIssues.toStrings("")).isEmpty();
+ assertThat(DefaultJRubyIssues.toStrings("foo")).containsOnly("foo");
+ assertThat(DefaultJRubyIssues.toStrings("foo,bar")).containsOnly("foo", "bar");
+ assertThat(DefaultJRubyIssues.toStrings(asList("foo","bar"))).containsOnly("foo", "bar");
+
+ }
+
+ @Test
public void should_start() throws Exception {
facade.start();
// nothing is done
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/services/IssueQuery.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/services/IssueQuery.java
index e64c38df6f8..e4d9fc44b59 100644
--- a/sonar-ws-client/src/main/java/org/sonar/wsclient/services/IssueQuery.java
+++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/services/IssueQuery.java
@@ -30,13 +30,11 @@ public final class IssueQuery extends Query<Issue> {
private String[] keys;
private String[] severities;
- private String minSeverity;
private String[] status;
private String[] resolutions;
private String[] components;
private String[] componentRoots;
- private String ruleRepository;
- private String rule;
+ private String[] rules;
private String[] userLogins;
private String[] assigneeLogins;
private Date createdAfter;
@@ -73,15 +71,6 @@ public final class IssueQuery extends Query<Issue> {
return this;
}
- public String getMinSeverity() {
- return minSeverity;
- }
-
- public IssueQuery setMinSeverity(String minSeverity) {
- this.minSeverity = minSeverity;
- return this;
- }
-
public String[] getStatus() {
return status;
}
@@ -118,21 +107,12 @@ public final class IssueQuery extends Query<Issue> {
return this;
}
- public String getRuleRepository() {
- return ruleRepository;
- }
-
- public IssueQuery setRuleRepository(String ruleRepository) {
- this.ruleRepository = ruleRepository;
- return this;
- }
-
- public String getRule() {
- return rule;
+ public String[] getRules() {
+ return rules;
}
- public IssueQuery setRule(String rule) {
- this.rule = rule;
+ public IssueQuery setRules(String... s) {
+ this.rules = s;
return this;
}
@@ -196,13 +176,11 @@ public final class IssueQuery extends Query<Issue> {
url.append('?');
appendUrlParameter(url, "keys", keys);
appendUrlParameter(url, "severities", severities);
- appendUrlParameter(url, "minSeverity", minSeverity);
- appendUrlParameter(url, "status", status);
+ appendUrlParameter(url, "statuses", status);
appendUrlParameter(url, "resolutions", resolutions);
appendUrlParameter(url, "components", components);
appendUrlParameter(url, "componentRoots", componentRoots);
- appendUrlParameter(url, "ruleRepository", ruleRepository);
- appendUrlParameter(url, "rule", rule);
+ appendUrlParameter(url, "rules", rules);
appendUrlParameter(url, "userLogins", userLogins);
appendUrlParameter(url, "assigneeLogins", assigneeLogins);
appendUrlParameter(url, "createdAfter", createdAfter);
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/services/IssueQueryTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/services/IssueQueryTest.java
index f0a89b5ff17..3489cbf1baf 100644
--- a/sonar-ws-client/src/test/java/org/sonar/wsclient/services/IssueQueryTest.java
+++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/services/IssueQueryTest.java
@@ -37,22 +37,20 @@ public class IssueQueryTest extends QueryTestCase {
@Test
public void get_all_issues_by_parameter() {
IssueQuery query = IssueQuery.create()
- .setKeys("key1", "key2")
- .setAssigneeLogins("assigneeLogin1", "assigneeLogin2")
+ .setKeys("ABCDE", "FGHIJ")
+ .setAssigneeLogins("arthur", "perceval")
.setComponents("component1", "component2")
.setComponentRoots("componentRoot1", "componentRoot2")
.setLimit(1)
- .setMinSeverity("minSev")
- .setResolutions("resoltion1", "resolution2")
- .setRuleRepository("ruleRepo")
- .setRule("rule")
- .setStatus("status1", "status2")
- .setSeverities("sev1", "sev2")
+ .setResolutions("resolution1", "resolution2")
+ .setRules("squid:AvoidCycle")
+ .setStatus("OPEN", "CLOSED")
+ .setSeverities("BLOCKER", "INFO")
.setUserLogins("userLogin1", "userLogin2")
;
- assertThat(query.getUrl()).isEqualTo("/api/issues/search?keys=key1,key2&severities=sev1,sev2&minSeverity=minSev&status=status1,status2&" +
- "resolutions=resoltion1,resolution2&components=component1,component2&componentRoots=componentRoot1,componentRoot2&ruleRepository=ruleRepo&rule=rule&" +
- "userLogins=userLogin1,userLogin2&assigneeLogins=assigneeLogin1,assigneeLogin2&limit=1&");
+ assertThat(query.getUrl()).isEqualTo("/api/issues/search?keys=ABCDE,FGHIJ&severities=BLOCKER,INFO&statuses=OPEN,CLOSED&" +
+ "resolutions=resolution1,resolution2&components=component1,component2&componentRoots=componentRoot1,componentRoot2&rules=squid%3AAvoidCycle&" +
+ "userLogins=userLogin1,userLogin2&assigneeLogins=arthur,perceval&limit=1&");
}
@Test