*/
public class RuleQuery {
- public static final int DEFAULT_PAGE_INDEX = 1;
+ private static final int DEFAULT_PAGE_INDEX = 1;
private static final int DEFAULT_PAGE_SIZE = 25;
private String key;
private String query;
- private String characteristic;
- private String language;
+ private Collection<String> languages;
private Collection<String> repositories;
+ private Collection<String> severities;
+ private Collection<String> statuses;
+ private Collection<String> tags;
+ private Collection<String> debtCharacteristics;
private int pageSize;
private int pageIndex;
private RuleQuery(Builder builder) {
this.key = builder.key;
this.query = builder.query;
- this.language = builder.language;
+ this.languages = defaultCollection(builder.languages);
this.repositories = defaultCollection(builder.repositories);
- this.characteristic = builder.characteristic;
+ this.severities = defaultCollection(builder.severities);
+ this.statuses = defaultCollection(builder.statuses);
+ this.tags = defaultCollection(builder.tags);
+ this.debtCharacteristics = defaultCollection(builder.debtCharacteristics);
this.pageSize = builder.pageSize;
this.pageIndex = builder.pageIndex;
}
return query;
}
- @CheckForNull
- public String language() {
- return language;
+ public Collection<String> languages() {
+ return languages;
}
public Collection<String> repositories() {
return repositories;
}
- @CheckForNull
- public String characteristic() {
- return characteristic;
+ public Collection<String> severities() {
+ return severities;
+ }
+
+ public Collection<String> statuses() {
+ return statuses;
+ }
+
+ public Collection<String> tags() {
+ return tags;
+ }
+
+ public Collection<String> debtCharacteristics() {
+ return debtCharacteristics;
}
public int pageSize() {
private String key;
private String query;
- private String characteristic;
- private String language;
+ private Collection<String> languages;
private Collection<String> repositories;
+ private Collection<String> severities;
+ private Collection<String> statuses;
+ private Collection<String> tags;
+ private Collection<String> debtCharacteristics;
private Integer pageSize;
private Integer pageIndex;
return this;
}
- public Builder language(@Nullable String language) {
- this.language = language;
+ public Builder languages(@Nullable Collection<String> languages) {
+ this.languages = languages;
return this;
}
- public Builder repositories(Collection<String> repositories) {
+ public Builder repositories(@Nullable Collection<String> repositories) {
this.repositories = repositories;
return this;
}
- public Builder characteristic(@Nullable String characteristic) {
- this.characteristic = characteristic;
+ public Builder severities(@Nullable Collection<String> severities) {
+ this.severities = severities;
+ return this;
+ }
+
+ public Builder statuses(@Nullable Collection<String> statuses) {
+ this.statuses = statuses;
+ return this;
+ }
+
+ public Builder tags(@Nullable Collection<String> tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ public Builder debtCharacteristics(@Nullable Collection<String> debtCharacteristics) {
+ this.debtCharacteristics = debtCharacteristics;
return this;
}
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.BoolFilterBuilder;
+import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.MatchQueryBuilder.Operator;
import org.elasticsearch.index.query.QueryBuilders;
mainFilter.must(FilterBuilders.queryFilter(
QueryBuilders.multiMatchQuery(query.query(), RuleDocument.FIELD_NAME + ".search", RuleDocument.FIELD_KEY).operator(Operator.AND)));
}
- if (query.characteristic() != null) {
+ addMustTermOrTerms(mainFilter, RuleDocument.FIELD_LANGUAGE, query.languages());
+ addMustTermOrTerms(mainFilter, RuleDocument.FIELD_REPOSITORY_KEY, query.repositories());
+ addMustTermOrTerms(mainFilter, RuleDocument.FIELD_SEVERITY, query.severities());
+ addMustTermOrTerms(mainFilter, RuleDocument.FIELD_STATUS, query.statuses());
+ if (!query.debtCharacteristics().isEmpty()) {
mainFilter.must(FilterBuilders.queryFilter(
- QueryBuilders.multiMatchQuery(query.characteristic(), RuleDocument.FIELD_CHARACTERISTIC_KEY, RuleDocument.FIELD_SUB_CHARACTERISTIC_KEY).operator(Operator.OR))
+ QueryBuilders.multiMatchQuery(query.debtCharacteristics(), RuleDocument.FIELD_CHARACTERISTIC_KEY, RuleDocument.FIELD_SUB_CHARACTERISTIC_KEY).operator(Operator.OR))
);
}
- if (query.language() != null) {
- mainFilter.must(termFilter(RuleDocument.FIELD_LANGUAGE, query.language()));
- }
- if (!query.repositories().isEmpty()) {
- if (query.repositories().size() == 1) {
- mainFilter.must(termFilter(RuleDocument.FIELD_REPOSITORY_KEY, query.repositories().iterator().next()));
- } else {
- mainFilter.must(termsFilter(RuleDocument.FIELD_REPOSITORY_KEY, query.repositories().toArray()));
- }
+ if (!query.tags().isEmpty()) {
+ mainFilter.must(FilterBuilders.queryFilter(
+ QueryBuilders.multiMatchQuery(query.tags(), RuleDocument.FIELD_ADMIN_TAGS, RuleDocument.FIELD_SYSTEM_TAGS).operator(Operator.OR))
+ );
}
+
Paging paging = Paging.create(query.pageSize(), query.pageIndex());
SearchHits hits = searchIndex.executeRequest(
searchIndex.client().prepareSearch(INDEX_RULES).setTypes(TYPE_RULE)
return new PagedResult<Rule>(rulesBuilder.build(), PagingResult.create(paging.pageSize(), paging.pageIndex(), hits.getTotalHits()));
}
+ private static void addMustTermOrTerms(BoolFilterBuilder filter, String field, Collection<String> terms) {
+ FilterBuilder termOrTerms = getTermOrTerms(field, terms);
+ if (termOrTerms != null) {
+ filter.must(termOrTerms);
+ }
+ }
+
+ private static FilterBuilder getTermOrTerms(String field, Collection<String> terms) {
+ if (terms.isEmpty()) {
+ return null;
+ } else {
+ if (terms.size() == 1) {
+ return termFilter(field, terms.iterator().next());
+ } else {
+ return termsFilter(field, terms.toArray());
+ }
+ }
+ }
+
/**
* Create or update definition of rule identified by <code>ruleId</code>
*/
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.RequestHandler;
import org.sonar.api.server.ws.Response;
import org.sonar.server.rule.Rule;
import org.sonar.server.rule.RuleQuery;
import org.sonar.server.rule.Rules;
+import org.sonar.server.util.RubyUtils;
import javax.annotation.CheckForNull;
if (ruleKeyParam == null) {
PagedResult<Rule> searchResult = rules.find(RuleQuery.builder()
.searchQuery(request.param("s"))
- .language(request.param("language"))
- .characteristic(request.param("characteristic"))
+ .languages(RubyUtils.toStrings(request.param("languages")))
+ .repositories(RubyUtils.toStrings(request.param("repositories")))
+ .severities(RubyUtils.toStrings(request.param("severities")))
+ .statuses(RubyUtils.toStrings(request.param("statuses")))
+ .tags(RubyUtils.toStrings(request.param("tags")))
+ .debtCharacteristics(RubyUtils.toStrings(request.param("debtCharacteristics")))
.pageSize(request.paramAsInt("ps"))
.pageIndex(request.paramAsInt("p"))
.build());
.prop("key", rule.ruleKey().toString())
.prop("name", rule.name())
.prop("language", languageName)
+ .prop("status", rule.status())
;
+ DebtRemediationFunction function = rule.debtRemediationFunction();
+ if (function != null) {
+ json
+ .prop("debtCharacteristic", rule.debtCharacteristicKey())
+ .prop("debtCharacteristicName", rule.debtCharacteristicName())
+ .prop("debtSubCharacteristic", rule.debtSubCharacteristicKey())
+ .prop("debtSubCharacteristicName", rule.debtSubCharacteristicName())
+ .prop("debtRemediationFunction", function.type().name())
+ .prop("debtRemediationCoefficient", function.coefficient())
+ .prop("debtRemediationOffset", function.offset())
+ ;
+ }
}
}
.setHandler(searchHandler)
.createParam("s", "An optional query that will be matched against rule titles.")
.createParam("k", "An optional query that will be matched exactly agains rule keys.")
+ // TODO add description for languages, repositories, etc. params
.createParam("ps", "Optional page size (default is 25).")
.createParam("p", "Optional page number (default is 0).");
// only static methods
}
+ @CheckForNull
public static List<String> toStrings(@Nullable Object o) {
List<String> result = null;
if (o != null) {
registry.bulkRegisterRules(rules, Maps.<Integer, CharacteristicDto>newHashMap(), params, tags);
assertThat(registry.findIds(ImmutableMap.of("repositoryKey", "repo"))).hasSize(2);
- Map<String, Object> rule2Document = esSetup.client().prepareGet("rules", "rule", Integer.toString(ruleId2))
- .execute().actionGet().getSourceAsMap();
+ Map<String, Object> rule2Document = esSetup.client().prepareGet("rules", "rule", Integer.toString(ruleId2)).execute().actionGet().getSourceAsMap();
assertThat((List<String>) rule2Document.get(RuleDocument.FIELD_SYSTEM_TAGS)).hasSize(2);
assertThat((List<String>) rule2Document.get(RuleDocument.FIELD_ADMIN_TAGS)).hasSize(1);
}
assertThat(esSetup.exists("rules", "rule", "3")).isFalse();
}
- @Test
- public void find_rules_by_name() {
- // Removed rule should not appear
- assertThat(registry.find(RuleQuery.builder().build()).results()).hasSize(2);
-
- // Search is case insensitive
- assertThat(registry.find(RuleQuery.builder().searchQuery("one issue per line").build()).results()).hasSize(1);
-
- // Search is ngram based
- assertThat(registry.find(RuleQuery.builder().searchQuery("with param").build()).results()).hasSize(1);
-
- // Search works also with key
- assertThat(registry.find(RuleQuery.builder().searchQuery("OneIssuePerLine").build()).results()).hasSize(1);
- }
-
- @Test
- public void find_rules_by_language() {
- assertThat(registry.find(RuleQuery.builder().language("xoo").build()).results()).hasSize(2);
- assertThat(registry.find(RuleQuery.builder().language("unknown").build()).results()).isEmpty();
- }
-
- @Test
- public void find_rules_by_rule_repositories() {
- assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("xoo")).build()).results()).hasSize(1);
- assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("xoo", "xoo2")).build()).results()).hasSize(2);
- assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("unknown")).build()).results()).isEmpty();
- }
-
@Test
public void index_debt_definitions() {
Map<Integer, CharacteristicDto> characteristics = newHashMap();
}
@Test
- public void find_rules_by_characteristic_or_sub_characteristic() {
- Map<Integer, CharacteristicDto> characteristics = newHashMap();
- characteristics.put(10, new CharacteristicDto().setId(10).setKey("REUSABILITY").setName("Reusability"));
- characteristics.put(11, new CharacteristicDto().setId(11).setKey("MODULARITY").setName("Modularity").setParentId(10));
+ public void find_rules_by_name() {
+ // Removed rule should not appear
+ assertThat(registry.find(RuleQuery.builder().searchQuery("Removed rule").build()).results()).isEmpty();
- List<RuleDto> rules = ImmutableList.of(new RuleDto().setId(10).setRepositoryKey("repo").setRuleKey("key1").setSeverity(Severity.MINOR)
- .setDefaultSubCharacteristicId(11).setDefaultRemediationFunction("LINEAR").setDefaultRemediationCoefficient("2h"));
+ // Search is case insensitive
+ assertThat(registry.find(RuleQuery.builder().searchQuery("one issue per line").build()).results()).hasSize(1);
+
+ // Search is ngram based
+ assertThat(registry.find(RuleQuery.builder().searchQuery("with param").build()).results()).hasSize(1);
+
+ // Search works also with key
+ assertThat(registry.find(RuleQuery.builder().searchQuery("OneIssuePerLine").build()).results()).hasSize(1);
+ }
+
+ @Test
+ public void find_rules_by_languages() {
+ assertThat(registry.find(RuleQuery.builder().languages(newArrayList("xoo")).build()).results()).hasSize(2);
+ assertThat(registry.find(RuleQuery.builder().languages(newArrayList("unknown")).build()).results()).isEmpty();
+ }
+
+ @Test
+ public void find_rules_by_repositories() {
+ assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("xoo")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("xoo", "xoo2")).build()).results()).hasSize(2);
+ assertThat(registry.find(RuleQuery.builder().repositories(newArrayList("unknown")).build()).results()).isEmpty();
+ }
+
+ @Test
+ public void find_rules_by_severities() {
+ assertThat(registry.find(RuleQuery.builder().severities(newArrayList("MAJOR")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().severities(newArrayList("MAJOR", "MINOR")).build()).results()).hasSize(2);
+ assertThat(registry.find(RuleQuery.builder().severities(newArrayList("unknown")).build()).results()).isEmpty();
+ }
- registry.bulkRegisterRules(rules, characteristics, ArrayListMultimap.<Integer, RuleParamDto>create(),
- ArrayListMultimap.<Integer, RuleRuleTagDto>create());
+ @Test
+ public void find_rules_by_statuses() {
+ assertThat(registry.find(RuleQuery.builder().statuses(newArrayList("READY")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().statuses(newArrayList("READY", "BETA")).build()).results()).hasSize(2);
+ assertThat(registry.find(RuleQuery.builder().statuses(newArrayList("unknown")).build()).results()).isEmpty();
+ }
- assertThat(registry.find(RuleQuery.builder().characteristic("MODULARITY").build()).results()).hasSize(1);
- assertThat(registry.find(RuleQuery.builder().characteristic("REUSABILITY").build()).results()).hasSize(1);
- assertThat(registry.find(RuleQuery.builder().characteristic("Unknown").build()).results()).isEmpty();
+ @Test
+ public void find_rules_by_tags() {
+ assertThat(registry.find(RuleQuery.builder().tags(newArrayList("has-params")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().tags(newArrayList("keep-enabled")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().tags(newArrayList("has-params", "keep-enabled")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().tags(newArrayList("unknown")).build()).results()).isEmpty();
+ }
+
+ @Test
+ public void find_rules_by_characteristics() {
+ assertThat(registry.find(RuleQuery.builder().debtCharacteristics(newArrayList("MODULARITY")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().debtCharacteristics(newArrayList("REUSABILITY")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().debtCharacteristics(newArrayList("MODULARITY", "REUSABILITY")).build()).results()).hasSize(1);
+ assertThat(registry.find(RuleQuery.builder().debtCharacteristics(newArrayList("unknown")).build()).results()).isEmpty();
}
private String testFileAsString(String testFile) throws Exception {
.setName("Avoid cycle")
.setDescription("Avoid cycle between packages")
.setLanguage("java")
+ .setStatus("READY")
.setDebtCharacteristicKey("REUSABILITY")
.setDebtCharacteristicName("Reusability")
- .setDebtCharacteristicKey("MODULARITY")
- .setDebtCharacteristicKey("Modularity")
+ .setDebtSubCharacteristicKey("MODULARITY")
+ .setDebtSubCharacteristicName("Modularity")
.setDebtRemediationFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "1h", "15min"));
WsTester tester;
MockUserSession.set();
WsTester.TestRequest request = tester.newRequest("list").setParam("ps", "10").setParam("p", "2");
- request.execute().assertJson("{'more':false,'total':1,'results':["
- + "{'key':'squid:AvoidCycle','name':'Avoid cycle','language':'Java'}"
- + "]}");
+ request.execute().assertJson(getClass(), "search_rules.json");
}
@Test
MockUserSession.set();
WsTester.TestRequest request = tester.newRequest("list").setParam("k", ruleKey);
- request.execute().assertJson("{'more':false,'total':1,'results':["
- + "{'key':'squid:AvoidCycle','name':'Avoid cycle','language':'Java'}"
- + "]}");
+ request.execute().assertJson(getClass(), "search_rules.json");
}
@Test
- public void search_rule_by_language() throws Exception {
+ public void verify_rule_query_filters() throws Exception {
Rule rule = ruleBuilder.build();
when(rules.find(any(RuleQuery.class))).thenReturn(
new PagedResult<Rule>(ImmutableList.of(rule), PagingResult.create(10, 1, 1)));
MockUserSession.set();
- WsTester.TestRequest request = tester.newRequest("list").setParam("language", "java");
- request.execute().assertJson("{'more':false,'total':1,'results':["
- + "{'key':'squid:AvoidCycle','name':'Avoid cycle','language':'java'}"
- + "]}");
+ tester.newRequest("list")
+ .setParam("languages", "java,js")
+ .setParam("repositories", "squid,pmd")
+ .setParam("severities", "MAJOR,MINOR")
+ .setParam("statuses", "READY,BETA")
+ .setParam("tags", "has-params,integration-tests")
+ .setParam("debtCharacteristics", "MODULARITY,REUSABILITY")
+ .execute();
ArgumentCaptor<RuleQuery> ruleQueryCaptor = ArgumentCaptor.forClass(RuleQuery.class);
verify(rules).find(ruleQueryCaptor.capture());
- assertThat(ruleQueryCaptor.getValue().language()).isEqualTo("java");
+ assertThat(ruleQueryCaptor.getValue().languages()).containsOnly("java", "js");
+ assertThat(ruleQueryCaptor.getValue().repositories()).containsOnly("squid", "pmd");
+ assertThat(ruleQueryCaptor.getValue().severities()).containsOnly("MAJOR", "MINOR");
+ assertThat(ruleQueryCaptor.getValue().statuses()).containsOnly("READY", "BETA");
+ assertThat(ruleQueryCaptor.getValue().tags()).containsOnly("has-params", "integration-tests");
+ assertThat(ruleQueryCaptor.getValue().debtCharacteristics()).containsOnly("MODULARITY", "REUSABILITY");
}
- @Test
- public void search_rule_by_characteristic() throws Exception {
- Rule rule = ruleBuilder.build();
-
- when(rules.find(any(RuleQuery.class))).thenReturn(
- new PagedResult<Rule>(ImmutableList.of(rule), PagingResult.create(10, 1, 1)));
-
- MockUserSession.set();
- WsTester.TestRequest request = tester.newRequest("list").setParam("characteristic", "MODULARITY");
- request.execute().assertJson("{'more':false,'total':1,'results':["
- + "{'key':'squid:AvoidCycle','name':'Avoid cycle','language':'java'}"
- + "]}");
-
- ArgumentCaptor<RuleQuery> ruleQueryCaptor = ArgumentCaptor.forClass(RuleQuery.class);
- verify(rules).find(ruleQueryCaptor.capture());
-
- assertThat(ruleQueryCaptor.getValue().characteristic()).isEqualTo("MODULARITY");
- }
}
"description": ""
}
],
- "defaultCharacteristicId": 1,
- "characteristicId": 2,
- "defaultRemediationFunction": "LINEAR",
+ "characteristicId": 1,
+ "characteristicKey": "REUSABILITY",
+ "characteristicName": "Reusability",
+ "subCharacteristicId": 2,
+ "subCharacteristicKey": "MODULARITY",
+ "subCharacteristicName": "Modularity",
"remediationFunction": "LINEAR_OFFSET",
- "defaultRemediationCoefficient": "2h",
"remediationCoefficient": "1h",
- "defaultRemediationOffset": null,
"remediationOffset": "15min"
}
"description": "Generate an issue on each line of a file. It requires the metric \"lines\".",
"parentKey": null,
"repositoryKey": "xoo2",
- "severity": "MAJOR",
+ "severity": "MINOR",
"status": "BETA",
"createdAt": "2013-10-28T13:07:26.339Z",
"updatedAt": "2013-11-08T10:52:53.487Z"
--- /dev/null
+{"more": false, "total": 1,
+ "results": [
+ {
+ "key": "squid:AvoidCycle",
+ "name": "Avoid cycle",
+ "language": "Java",
+ "status": "READY",
+ "debtCharacteristic": "REUSABILITY",
+ "debtCharacteristicName": "Reusability",
+ "debtSubCharacteristic": "MODULARITY",
+ "debtSubCharacteristicName": "Modularity",
+ "debtRemediationFunction": "LINEAR_OFFSET",
+ "debtRemediationCoefficient": "1h",
+ "debtRemediationOffset": "15min"
+ }
+ ]
+}