diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-04-17 18:29:35 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-04-17 18:36:56 +0200 |
commit | 7660b174b186527b5df5354295886c42dec2109d (patch) | |
tree | 0ed5f14607232f9161741a486e5d67ca48440e93 | |
parent | 398c82a5810e446a22b31d204a87ad7fd806a068 (diff) | |
download | sonarqube-7660b174b186527b5df5354295886c42dec2109d.tar.gz sonarqube-7660b174b186527b5df5354295886c42dec2109d.zip |
SONAR-3755 support the parameter "rules" in /api/issues/search
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 > #{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 |