]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9483 Add "compareToProfile" in rules search ws
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 30 Jun 2017 07:27:37 +0000 (09:27 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 4 Jul 2017 14:29:36 +0000 (16:29 +0200)
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java
server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json [deleted file]
server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/rule/RulesWsParameters.java

index 376d2e4f15330708b85d308e1887e33caeb5c89c..0046aa9190433d4f97954ea03f1613eee47828e2 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.rule.ws;
 
 import com.google.common.collect.ImmutableList;
 import java.util.Date;
-import java.util.List;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.server.ServerSide;
@@ -32,13 +31,16 @@ import org.sonar.db.DbSession;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.server.rule.index.RuleQuery;
-import org.sonar.server.ws.WsUtils;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
 import static org.sonar.server.util.EnumUtils.toEnums;
+import static org.sonar.server.ws.WsUtils.checkFound;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES;
@@ -78,30 +80,12 @@ public class RuleQueryFactory {
     Boolean activation = request.paramAsBoolean(PARAM_ACTIVATION);
     query.setActivation(activation);
 
-    String profileUuid = request.param(PARAM_QPROFILE);
-    String organizationKey = request.param(PARAM_ORGANIZATION);
-    OrganizationDto organization;
-    List<String> languages;
-    if (profileUuid == null) {
-      organization = wsSupport.getOrganizationByKey(dbSession, organizationKey);
-      languages = request.paramAsStrings(PARAM_LANGUAGES);
-    } else {
-      QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, profileUuid);
-      QProfileDto profile = WsUtils.checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", profileUuid);
-      query.setQProfile(profile);
-      languages = ImmutableList.of(profile.getLanguage());
-      organization = WsUtils.checkFoundWithOptional(dbClient.organizationDao().selectByUuid(dbSession, profile.getOrganizationUuid()), "No organization with UUID ",
-        profile.getOrganizationUuid());
-      if (organizationKey != null) {
-        OrganizationDto inputOrganization = WsUtils.checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'",
-          organizationKey);
-        if (!organization.getUuid().equals(inputOrganization.getUuid())) {
-          throw new IllegalArgumentException(format("The specified quality profile '%s' is not part of the specified organization '%s'", profileUuid, organizationKey));
-        }
-      }
-    }
-    query.setOrganization(organization);
-    query.setLanguages(languages);
+    // Order is important : 1. Load profile, 2. Load organization either from parameter or from profile, 3. Load compare to profile
+    setProfile(dbSession, query, request);
+    setOrganization(dbSession, query, request);
+    setCompareToProfile(dbSession, query, request);
+    QProfileDto profile = query.getQProfile();
+    query.setLanguages(profile == null ? request.paramAsStrings(PARAM_LANGUAGES) : ImmutableList.of(profile.getLanguage()));
 
     query.setTags(request.paramAsStrings(PARAM_TAGS));
     query.setInheritance(request.paramAsStrings(PARAM_INHERITANCE));
@@ -118,4 +102,46 @@ public class RuleQueryFactory {
     }
     return query;
   }
+
+  private void setProfile(DbSession dbSession, RuleQuery query, Request request) {
+    String profileUuid = request.param(PARAM_QPROFILE);
+    if (profileUuid == null) {
+      return;
+    }
+    QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, profileUuid);
+    QProfileDto profile = checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", profileUuid);
+    query.setQProfile(profile);
+  }
+
+  private void setOrganization(DbSession dbSession, RuleQuery query, Request request) {
+    String organizationKey = request.param(PARAM_ORGANIZATION);
+    QProfileDto profile = query.getQProfile();
+    if (profile == null) {
+      query.setOrganization(wsSupport.getOrganizationByKey(dbSession, organizationKey));
+      return;
+    }
+    OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByUuid(dbSession, profile.getOrganizationUuid()), "No organization with UUID ",
+      profile.getOrganizationUuid());
+    if (organizationKey != null) {
+      OrganizationDto inputOrganization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'",
+        organizationKey);
+      checkArgument(organization.getUuid().equals(inputOrganization.getUuid()),
+        format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), organizationKey));
+    }
+    query.setOrganization(organization);
+  }
+
+  private void setCompareToProfile(DbSession dbSession, RuleQuery query, Request request) {
+    String compareToProfileUuid = request.param(PARAM_COMPARE_TO_PROFILE);
+    if (compareToProfileUuid == null) {
+      return;
+    }
+    QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, compareToProfileUuid);
+    QProfileDto profile = checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", compareToProfileUuid);
+
+    checkArgument(query.getOrganization().getUuid().equals(profile.getOrganizationUuid()),
+      format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), query.getOrganization().getKey()));
+
+    query.setCompareToQProfile(profile);
+  }
 }
index 51fe97c7090116eea9743f6e7ae48e76bb1c5b0d..fb5a05efabce27d4543b2ea5d8964f77c20665b9 100644 (file)
@@ -26,7 +26,6 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
-import com.google.common.io.Resources;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -69,6 +68,8 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE;
 import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.api.server.ws.WebService.Param.SORT;
 import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
 import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED;
 import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES;
@@ -84,6 +85,7 @@ import static org.sonarqube.ws.client.rule.RulesWsParameters.OPTIONAL_FIELDS;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES;
@@ -148,7 +150,6 @@ public class SearchAction implements RulesWsAction {
       .setPossibleValues(Ordering.natural().sortedCopy(OPTIONAL_FIELDS));
     Iterator<String> it = OPTIONAL_FIELDS.iterator();
     paramFields.setExampleValue(format("%s,%s", it.next(), it.next()));
-
     doDefinition(action);
   }
 
@@ -191,7 +192,7 @@ public class SearchAction implements RulesWsAction {
       "<li>\"defaultDebtRemFnOffset\" becomes \"defaultRemFnBaseEffort\"</li>" +
       "<li>\"debtOverloaded\" becomes \"remFnOverloaded\"</li>" +
       "</ul>")
-      .setResponseExample(Resources.getResource(getClass(), "example-search.json"))
+      .setResponseExample(getClass().getResource("search-example.json"))
       .setSince("4.4")
       .setHandler(this);
 
@@ -257,9 +258,15 @@ public class SearchAction implements RulesWsAction {
 
     action
       .createParam(PARAM_QPROFILE)
-      .setDescription("Key of Quality profile to filter on. Used only if the parameter '" +
+      .setDescription("Quality profile key to filter on. Used only if the parameter '" +
         PARAM_ACTIVATION + "' is set.")
-      .setExampleValue("sonar-way-cs-12345");
+      .setExampleValue(UUID_EXAMPLE_01);
+
+    action.createParam(PARAM_COMPARE_TO_PROFILE)
+      .setDescription("Quality profile key to filter rules that are activated. Meant to compare easily to profile set in '%s'", PARAM_QPROFILE)
+      .setInternal(true)
+      .setSince("6.5")
+      .setExampleValue(UUID_EXAMPLE_02);
 
     action
       .createParam(PARAM_INHERITANCE)
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json b/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json
deleted file mode 100644 (file)
index 174745c..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-{
-  "total": 4,
-  "p": 1,
-  "ps": 3,
-  "rules": [
-    {
-      "key": "squid:S1067",
-      "repo": "squid",
-      "name": "Expressions should not be too complex",
-      "createdAt": "2013-03-27T08:52:40+0100",
-      "htmlDesc": "<p>\nThe complexity of an expression is defined by the number of <code>&&</code>, <code>||</code> and <code>condition ? ifTrue : ifFalse</code> operators it contains.\nA single expression's complexity should not become too high to keep the code readable.\n</p>\n\n<p>The following code, with a maximum complexity of 3:</p>\n\n<pre>\nif (condition1 && condition2 && condition3 && condition4) { /* ... */ }  // Non-Compliant\n</pre>\n\n<p>could be refactored into something like:</p>\n\n<pre>\nif (relevantMethodName1() && relevantMethodName2()) { /* ... */ }        // Compliant\n\n/* ... */\n\nprivate boolean relevantMethodName1() {\n  return condition1 && condition2;\n}\n\nprivate boolean relevantMethodName2() {\n  return condition3 && condition4;\n}\n</pre>",
-      "severity": "MAJOR",
-      "status": "READY",
-      "internalKey": "S1067",
-      "isTemplate": false,
-      "tags": [],
-      "sysTags": ["brain-overload"],
-      "lang": "java",
-      "langName": "Java",
-      "type": "CODE_SMELL",
-      "params": [
-        {
-          "key": "max",
-          "desc": "Maximum number of allowed conditional operators in an expression",
-          "defaultValue": "3"
-        }
-      ]
-    },
-    {
-      "key": "squid:ClassCyclomaticComplexity",
-      "repo": "squid",
-      "name": "Avoid too complex class",
-      "createdAt": "2013-03-27T08:52:40+0100",
-      "htmlDesc": "<p>The Cyclomatic Complexity is measured by the number of (&&, ||)\n\toperators and (if, while, do, for, ?:, catch, switch, case, return,\n\tthrow) statements in the body of a class plus one for each constructor,\n\tmethod (but not getter/setter), static initializer, or instance\n\tinitializer in the class. The last return stament in method, if exists,\n\tis not taken into account.</p>\n<p>\n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the <a\n\t\thref='http://en.wikipedia.org/wiki/Single_responsibility_principle'>Single\n\t\tResponsibility Principle</a> and which should be re-factored to be split\n\tin several classes.\n</p>",
-      "severity": "MAJOR",
-      "status": "READY",
-      "internalKey": "ClassCyclomaticComplexity",
-      "isTemplate": false,
-      "tags": [],
-      "sysTags": ["brain-overload"],
-      "lang": "java",
-      "langName": "Java",
-      "type": "BUG",
-      "params": [
-        {
-          "key": "max",
-          "desc": "Maximum complexity allowed.",
-          "defaultValue": "200"
-        }
-      ]
-    },
-    {
-      "key": "squid:MethodCyclomaticComplexity",
-      "repo": "squid",
-      "name": "Methods should not be too complex",
-      "createdAt": "2013-03-27T08:52:40+0100",
-      "htmlDesc": "<p>The Cyclomatic Complexity is measured by the number of\n\t(&amp;&amp;, ||) operators and (if, while, do, for, ?:, catch, switch,\n\tcase, return, throw) statements in the body of a class plus one for\n\teach constructor, method (but not getter/setter), static initializer,\n\tor instance initializer in the class. The last return stament in\n\tmethod, if exists, is not taken into account.</p>\n<p>\n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the <a\n\t\thref=\"http://en.wikipedia.org/wiki/Single_responsibility_principle\">Single\n\t\tResponsibility Principle</a> and which should be re-factored to be split\n\tin several classes.\n</p>",
-      "severity": "MAJOR",
-      "status": "READY",
-      "internalKey": "MethodCyclomaticComplexity",
-      "isTemplate": false,
-      "tags": [],
-      "sysTags": ["brain-overload"],
-      "lang": "java",
-      "langName": "Java",
-      "type": "VULNERABILITY",
-      "params": [
-        {
-          "key": "max",
-          "desc": "Maximum complexity allowed.",
-          "defaultValue": "10"
-        }
-      ]
-    },
-    {
-      "key": "squid:XPath",
-      "repo": "squid",
-      "name": "XPath rule",
-      "createdAt": "2013-03-27T08:52:40+0100",
-      "htmlDesc": "<p>\nThis rule allows to define some homemade Java rules with help of an XPath expression.\n</p>\n\n<p>\nIssues are created depending on the return value of the XPath expression. If the XPath expression returns:\n</p>\n<ul>\n <li>a single or list of AST nodes, then a line issue with the given message is created for each node</li>\n <li>a boolean, then a file issue with the given message is created only if the boolean is true</li>\n <li>anything else, no issue is created</li>\n</ul>\n\n<p>\nHere is an example of an XPath expression to log an issue on each if statement : //ifStatement\n</p>",
-      "severity": "MAJOR",
-      "status": "READY",
-      "internalKey": "XPath",
-      "isTemplate": true,
-      "tags": [ ],
-      "sysTags": [ ],
-      "mdNote": "<p>\nThe tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.\n</p>\n\n<p>\nConsider the following rule:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n b.firstOf( /* Non-Compliant */\n \"FOO\",\n \"BAR\"));\n</pre>\n\n<p>\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\nthe easiest option will be to verify that the value of the first token is \"BAR\",\ni.e. <code>\"BAR\".equals(compilationUnitNode.getTokenValue())</code>.\n</p>\n\n<p>\nThis is not maintainable, for at least two reasons:\n</p>\n\n<ul>\n <li>The grammar might evolve to also accept \"bar\" in lowercase, which will break <code>\"BAR\".equals(...)</code></li>\n <li>The grammar might evolve to optionally accept \"hello\" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>\n</ul>\n\n<p>\nInstead, it is much better to rewrite the grammar as:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n firstOf( /* Compliant */\n FOO,\n BAR));\nb.rule(FOO).is(\"FOO\");\nb.rule(BAR).is(\"BAR\");\n</pre>\n\n<p>\nThe same check which forbids \"BAR\" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\n</p>",
-      "htmlNote": "&lt;p&gt;<br/>The tree produced by the &lt;code&gt;firstOf()&lt;/code&gt; matcher is hard to work with from checks when alternatives are not named.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>Consider the following rule:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> b.firstOf( /* Non-Compliant */<br/> &quot;FOO&quot;,<br/> &quot;BAR&quot;));<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>If, from a check, one wants to forbid the usage of the &quot;BAR&quot; alternative,<br/>the easiest option will be to verify that the value of the first token is &quot;BAR&quot;,<br/>i.e. &lt;code&gt;&quot;BAR&quot;.equals(compilationUnitNode.getTokenValue())&lt;/code&gt;.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>This is not maintainable, for at least two reasons:<br/>&lt;/p&gt;<br/><br/>&lt;ul&gt;<br/> &lt;li&gt;The grammar might evolve to also accept &quot;bar&quot; in lowercase, which will break &lt;code&gt;&quot;BAR&quot;.equals(...)&lt;/code&gt;&lt;/li&gt;<br/> &lt;li&gt;The grammar might evolve to optionally accept &quot;hello&quot; before the &lt;code&gt;firstOf()&lt;/code&gt;, which will break &lt;code&gt;compilationUnitNode.getTokenValue()&lt;/code&gt;&lt;/li&gt;<br/>&lt;/ul&gt;<br/><br/>&lt;p&gt;<br/>Instead, it is much better to rewrite the grammar as:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> firstOf( /* Compliant */<br/> FOO,<br/> BAR));<br/>b.rule(FOO).is(&quot;FOO&quot;);<br/>b.rule(BAR).is(&quot;BAR&quot;);<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>The same check which forbids &quot;BAR&quot; would be written as: &lt;code&gt;compilationUnitNode.hasDirectChildren(BAR)&lt;/code&gt;.<br/>This allows both of the previous grammar evolutions to be made without impacting the check at all.<br/>&lt;/p&gt;",
-      "noteLogin": "eric.hartmann",
-      "lang": "java",
-      "langName": "Java",
-      "type": "CODE_SMELL",
-      "params": [
-        {
-          "key": "xpathQuery",
-          "desc": "The XPath query",
-          "defaultValue": ""
-        },
-        {
-          "key": "message",
-          "desc": "The violation message",
-          "defaultValue": "The XPath expression matches this piece of code"
-        }
-      ]
-    },
-    {
-      "key": "squid:XPath_1369910135",
-      "repo": "squid",
-      "name": "firstOf() alternatives should be rules or token types",
-      "createdAt": "2013-05-30T10:35:35+0200",
-      "htmlDesc": "<p>\r\nThe tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.\r\n</p>\r\n\r\n<p>\r\nConsider the following rule:\r\n</p>\r\n\r\n<pre>\r\nb.rule(COMPILATION_UNIT).is(\r\n b.firstOf( /* Non-Compliant */\r\n \"FOO\",\r\n \"BAR\"));\r\n</pre>\r\n\r\n<p>\r\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\r\nthe easiest option will be to verify that the value of the first token is \"BAR\",\r\ni.e. <code>\"BAR\".equals(compilationUnitNode.getTokenValue())</code>.\r\n</p>\r\n\r\n<p>\r\nThis is not maintainable, for at least two reasons:\r\n</p>\r\n\r\n<ul>\r\n <li>The grammar might evolve to also accept \"bar\" in lowercase, which will break <code>\"BAR\".equals(...)</code></li>\r\n <li>The grammar might evolve to optionally accept \"hello\" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>\r\n</ul>\r\n\r\n<p>\r\nInstead, it is much better to rewrite the grammar as:\r\n</p>\r\n\r\n<pre>\r\nb.rule(COMPILATION_UNIT).is(\r\n firstOf( /* Compliant */\r\n FOO,\r\n BAR));\r\nb.rule(FOO).is(\"FOO\");\r\nb.rule(BAR).is(\"BAR\");\r\n</pre>\r\n\r\n<p>\r\nThe same check which forbids \"BAR\" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.\r\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\r\n</p>",
-      "severity": "MAJOR",
-      "status": "READY",
-      "internalKey": "XPath",
-      "isTemplate": false,
-      "templateKey": "squid:XPath",
-      "tags": [ ],
-      "sysTags": [ ],
-      "lang": "java",
-      "langName": "Java",
-      "type": "CODE_SMELL",
-      "params": [
-        {
-          "key": "xpathQuery",
-          "desc": "The XPath query",
-          "defaultValue": "//expression[primary/qualifiedIdentifier[count(IDENTIFIER) = 2]/IDENTIFIER[2]/@tokenValue = 'firstOf' and primary/identifierSuffix/arguments/expression[not(primary) or primary[not(qualifiedIdentifier) or identifierSuffix]]]"
-        },
-        {
-          "key": "message",
-          "desc": "The violation message",
-          "defaultValue": "Refactor this firstOf() to only use a rule or token type for each alternative."
-        }
-      ]
-    }
-  ],
-  "actives": {
-    "squid:MethodCyclomaticComplexity": [
-      {
-        "qProfile": "Sonar way with Findbugs:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "10"
-          }
-        ]
-      },
-      {
-        "qProfile": "Sonar way:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "10"
-          }
-        ]
-      }
-    ],
-    "squid:S1067": [
-      {
-        "qProfile": "Sonar way with Findbugs:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "3"
-          }
-        ]
-      },
-      {
-        "qProfile": "Sonar way:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "3"
-          }
-        ]
-      }
-    ],
-    "squid:ClassCyclomaticComplexity": [
-      {
-        "qProfile": "Sonar way with Findbugs:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "200"
-          }
-        ]
-      },
-      {
-        "qProfile": "Sonar way:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-          {
-            "key": "max",
-            "value": "200"
-          }
-        ]
-      }
-    ]
-  },
-  "facets": [
-    {
-      "name": "tags",
-      "values": [
-        {
-          "val": "complexity",
-          "count": 141
-        },
-        {
-          "val": "java8",
-          "count": 42
-        },
-        {
-          "val": "javadoc",
-          "count": 13
-        }
-      ]
-    },
-    {
-      "name": "languages",
-      "values": [
-        {
-          "val": "java",
-          "count": 563
-        }
-      ]
-    },
-    {
-      "name": "repositories",
-      "values": [
-        {
-          "val": "findbugs",
-          "count": 419
-        },
-        {
-          "val": "squid",
-          "count": 138
-        },
-        {
-          "val": "common-java",
-          "count": 6
-        }
-      ]
-    }
-
-  ]
-}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json
new file mode 100644 (file)
index 0000000..174745c
--- /dev/null
@@ -0,0 +1,256 @@
+{
+  "total": 4,
+  "p": 1,
+  "ps": 3,
+  "rules": [
+    {
+      "key": "squid:S1067",
+      "repo": "squid",
+      "name": "Expressions should not be too complex",
+      "createdAt": "2013-03-27T08:52:40+0100",
+      "htmlDesc": "<p>\nThe complexity of an expression is defined by the number of <code>&&</code>, <code>||</code> and <code>condition ? ifTrue : ifFalse</code> operators it contains.\nA single expression's complexity should not become too high to keep the code readable.\n</p>\n\n<p>The following code, with a maximum complexity of 3:</p>\n\n<pre>\nif (condition1 && condition2 && condition3 && condition4) { /* ... */ }  // Non-Compliant\n</pre>\n\n<p>could be refactored into something like:</p>\n\n<pre>\nif (relevantMethodName1() && relevantMethodName2()) { /* ... */ }        // Compliant\n\n/* ... */\n\nprivate boolean relevantMethodName1() {\n  return condition1 && condition2;\n}\n\nprivate boolean relevantMethodName2() {\n  return condition3 && condition4;\n}\n</pre>",
+      "severity": "MAJOR",
+      "status": "READY",
+      "internalKey": "S1067",
+      "isTemplate": false,
+      "tags": [],
+      "sysTags": ["brain-overload"],
+      "lang": "java",
+      "langName": "Java",
+      "type": "CODE_SMELL",
+      "params": [
+        {
+          "key": "max",
+          "desc": "Maximum number of allowed conditional operators in an expression",
+          "defaultValue": "3"
+        }
+      ]
+    },
+    {
+      "key": "squid:ClassCyclomaticComplexity",
+      "repo": "squid",
+      "name": "Avoid too complex class",
+      "createdAt": "2013-03-27T08:52:40+0100",
+      "htmlDesc": "<p>The Cyclomatic Complexity is measured by the number of (&&, ||)\n\toperators and (if, while, do, for, ?:, catch, switch, case, return,\n\tthrow) statements in the body of a class plus one for each constructor,\n\tmethod (but not getter/setter), static initializer, or instance\n\tinitializer in the class. The last return stament in method, if exists,\n\tis not taken into account.</p>\n<p>\n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the <a\n\t\thref='http://en.wikipedia.org/wiki/Single_responsibility_principle'>Single\n\t\tResponsibility Principle</a> and which should be re-factored to be split\n\tin several classes.\n</p>",
+      "severity": "MAJOR",
+      "status": "READY",
+      "internalKey": "ClassCyclomaticComplexity",
+      "isTemplate": false,
+      "tags": [],
+      "sysTags": ["brain-overload"],
+      "lang": "java",
+      "langName": "Java",
+      "type": "BUG",
+      "params": [
+        {
+          "key": "max",
+          "desc": "Maximum complexity allowed.",
+          "defaultValue": "200"
+        }
+      ]
+    },
+    {
+      "key": "squid:MethodCyclomaticComplexity",
+      "repo": "squid",
+      "name": "Methods should not be too complex",
+      "createdAt": "2013-03-27T08:52:40+0100",
+      "htmlDesc": "<p>The Cyclomatic Complexity is measured by the number of\n\t(&amp;&amp;, ||) operators and (if, while, do, for, ?:, catch, switch,\n\tcase, return, throw) statements in the body of a class plus one for\n\teach constructor, method (but not getter/setter), static initializer,\n\tor instance initializer in the class. The last return stament in\n\tmethod, if exists, is not taken into account.</p>\n<p>\n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the <a\n\t\thref=\"http://en.wikipedia.org/wiki/Single_responsibility_principle\">Single\n\t\tResponsibility Principle</a> and which should be re-factored to be split\n\tin several classes.\n</p>",
+      "severity": "MAJOR",
+      "status": "READY",
+      "internalKey": "MethodCyclomaticComplexity",
+      "isTemplate": false,
+      "tags": [],
+      "sysTags": ["brain-overload"],
+      "lang": "java",
+      "langName": "Java",
+      "type": "VULNERABILITY",
+      "params": [
+        {
+          "key": "max",
+          "desc": "Maximum complexity allowed.",
+          "defaultValue": "10"
+        }
+      ]
+    },
+    {
+      "key": "squid:XPath",
+      "repo": "squid",
+      "name": "XPath rule",
+      "createdAt": "2013-03-27T08:52:40+0100",
+      "htmlDesc": "<p>\nThis rule allows to define some homemade Java rules with help of an XPath expression.\n</p>\n\n<p>\nIssues are created depending on the return value of the XPath expression. If the XPath expression returns:\n</p>\n<ul>\n <li>a single or list of AST nodes, then a line issue with the given message is created for each node</li>\n <li>a boolean, then a file issue with the given message is created only if the boolean is true</li>\n <li>anything else, no issue is created</li>\n</ul>\n\n<p>\nHere is an example of an XPath expression to log an issue on each if statement : //ifStatement\n</p>",
+      "severity": "MAJOR",
+      "status": "READY",
+      "internalKey": "XPath",
+      "isTemplate": true,
+      "tags": [ ],
+      "sysTags": [ ],
+      "mdNote": "<p>\nThe tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.\n</p>\n\n<p>\nConsider the following rule:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n b.firstOf( /* Non-Compliant */\n \"FOO\",\n \"BAR\"));\n</pre>\n\n<p>\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\nthe easiest option will be to verify that the value of the first token is \"BAR\",\ni.e. <code>\"BAR\".equals(compilationUnitNode.getTokenValue())</code>.\n</p>\n\n<p>\nThis is not maintainable, for at least two reasons:\n</p>\n\n<ul>\n <li>The grammar might evolve to also accept \"bar\" in lowercase, which will break <code>\"BAR\".equals(...)</code></li>\n <li>The grammar might evolve to optionally accept \"hello\" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>\n</ul>\n\n<p>\nInstead, it is much better to rewrite the grammar as:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n firstOf( /* Compliant */\n FOO,\n BAR));\nb.rule(FOO).is(\"FOO\");\nb.rule(BAR).is(\"BAR\");\n</pre>\n\n<p>\nThe same check which forbids \"BAR\" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\n</p>",
+      "htmlNote": "&lt;p&gt;<br/>The tree produced by the &lt;code&gt;firstOf()&lt;/code&gt; matcher is hard to work with from checks when alternatives are not named.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>Consider the following rule:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> b.firstOf( /* Non-Compliant */<br/> &quot;FOO&quot;,<br/> &quot;BAR&quot;));<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>If, from a check, one wants to forbid the usage of the &quot;BAR&quot; alternative,<br/>the easiest option will be to verify that the value of the first token is &quot;BAR&quot;,<br/>i.e. &lt;code&gt;&quot;BAR&quot;.equals(compilationUnitNode.getTokenValue())&lt;/code&gt;.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>This is not maintainable, for at least two reasons:<br/>&lt;/p&gt;<br/><br/>&lt;ul&gt;<br/> &lt;li&gt;The grammar might evolve to also accept &quot;bar&quot; in lowercase, which will break &lt;code&gt;&quot;BAR&quot;.equals(...)&lt;/code&gt;&lt;/li&gt;<br/> &lt;li&gt;The grammar might evolve to optionally accept &quot;hello&quot; before the &lt;code&gt;firstOf()&lt;/code&gt;, which will break &lt;code&gt;compilationUnitNode.getTokenValue()&lt;/code&gt;&lt;/li&gt;<br/>&lt;/ul&gt;<br/><br/>&lt;p&gt;<br/>Instead, it is much better to rewrite the grammar as:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> firstOf( /* Compliant */<br/> FOO,<br/> BAR));<br/>b.rule(FOO).is(&quot;FOO&quot;);<br/>b.rule(BAR).is(&quot;BAR&quot;);<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>The same check which forbids &quot;BAR&quot; would be written as: &lt;code&gt;compilationUnitNode.hasDirectChildren(BAR)&lt;/code&gt;.<br/>This allows both of the previous grammar evolutions to be made without impacting the check at all.<br/>&lt;/p&gt;",
+      "noteLogin": "eric.hartmann",
+      "lang": "java",
+      "langName": "Java",
+      "type": "CODE_SMELL",
+      "params": [
+        {
+          "key": "xpathQuery",
+          "desc": "The XPath query",
+          "defaultValue": ""
+        },
+        {
+          "key": "message",
+          "desc": "The violation message",
+          "defaultValue": "The XPath expression matches this piece of code"
+        }
+      ]
+    },
+    {
+      "key": "squid:XPath_1369910135",
+      "repo": "squid",
+      "name": "firstOf() alternatives should be rules or token types",
+      "createdAt": "2013-05-30T10:35:35+0200",
+      "htmlDesc": "<p>\r\nThe tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.\r\n</p>\r\n\r\n<p>\r\nConsider the following rule:\r\n</p>\r\n\r\n<pre>\r\nb.rule(COMPILATION_UNIT).is(\r\n b.firstOf( /* Non-Compliant */\r\n \"FOO\",\r\n \"BAR\"));\r\n</pre>\r\n\r\n<p>\r\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\r\nthe easiest option will be to verify that the value of the first token is \"BAR\",\r\ni.e. <code>\"BAR\".equals(compilationUnitNode.getTokenValue())</code>.\r\n</p>\r\n\r\n<p>\r\nThis is not maintainable, for at least two reasons:\r\n</p>\r\n\r\n<ul>\r\n <li>The grammar might evolve to also accept \"bar\" in lowercase, which will break <code>\"BAR\".equals(...)</code></li>\r\n <li>The grammar might evolve to optionally accept \"hello\" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>\r\n</ul>\r\n\r\n<p>\r\nInstead, it is much better to rewrite the grammar as:\r\n</p>\r\n\r\n<pre>\r\nb.rule(COMPILATION_UNIT).is(\r\n firstOf( /* Compliant */\r\n FOO,\r\n BAR));\r\nb.rule(FOO).is(\"FOO\");\r\nb.rule(BAR).is(\"BAR\");\r\n</pre>\r\n\r\n<p>\r\nThe same check which forbids \"BAR\" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.\r\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\r\n</p>",
+      "severity": "MAJOR",
+      "status": "READY",
+      "internalKey": "XPath",
+      "isTemplate": false,
+      "templateKey": "squid:XPath",
+      "tags": [ ],
+      "sysTags": [ ],
+      "lang": "java",
+      "langName": "Java",
+      "type": "CODE_SMELL",
+      "params": [
+        {
+          "key": "xpathQuery",
+          "desc": "The XPath query",
+          "defaultValue": "//expression[primary/qualifiedIdentifier[count(IDENTIFIER) = 2]/IDENTIFIER[2]/@tokenValue = 'firstOf' and primary/identifierSuffix/arguments/expression[not(primary) or primary[not(qualifiedIdentifier) or identifierSuffix]]]"
+        },
+        {
+          "key": "message",
+          "desc": "The violation message",
+          "defaultValue": "Refactor this firstOf() to only use a rule or token type for each alternative."
+        }
+      ]
+    }
+  ],
+  "actives": {
+    "squid:MethodCyclomaticComplexity": [
+      {
+        "qProfile": "Sonar way with Findbugs:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "10"
+          }
+        ]
+      },
+      {
+        "qProfile": "Sonar way:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "10"
+          }
+        ]
+      }
+    ],
+    "squid:S1067": [
+      {
+        "qProfile": "Sonar way with Findbugs:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "3"
+          }
+        ]
+      },
+      {
+        "qProfile": "Sonar way:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "3"
+          }
+        ]
+      }
+    ],
+    "squid:ClassCyclomaticComplexity": [
+      {
+        "qProfile": "Sonar way with Findbugs:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "200"
+          }
+        ]
+      },
+      {
+        "qProfile": "Sonar way:java",
+        "inherit": "NONE",
+        "severity": "MAJOR",
+        "params": [
+          {
+            "key": "max",
+            "value": "200"
+          }
+        ]
+      }
+    ]
+  },
+  "facets": [
+    {
+      "name": "tags",
+      "values": [
+        {
+          "val": "complexity",
+          "count": 141
+        },
+        {
+          "val": "java8",
+          "count": 42
+        },
+        {
+          "val": "javadoc",
+          "count": 13
+        }
+      ]
+    },
+    {
+      "name": "languages",
+      "values": [
+        {
+          "val": "java",
+          "count": 563
+        }
+      ]
+    },
+    {
+      "name": "repositories",
+      "values": [
+        {
+          "val": "findbugs",
+          "count": 419
+        },
+        {
+          "val": "squid",
+          "count": 138
+        },
+        {
+          "val": "common-java",
+          "count": 6
+        }
+      ]
+    }
+
+  ]
+}
index 1074eda558a0deb15e4401ce794bc164154f0db3..0e475029e2367e772e23e67777589875ad25fca1 100644 (file)
@@ -82,6 +82,7 @@ public class ActivateRulesActionTest {
       "is_template",
       "inheritance",
       "qprofile",
+      "compareToProfile",
       "targetSeverity",
       "tags",
       "asc",
index 7936fb0387a2318ee61ab22254bc14b26c552b04..0312273fc84ad38b97b5ad683229dec7d49497f9 100644 (file)
@@ -28,8 +28,8 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
@@ -81,6 +81,7 @@ public class DeactivateRulesActionTest {
       "is_template",
       "inheritance",
       "qprofile",
+      "compareToProfile",
       "tags",
       "asc",
       "q",
index 1010fc65c4989c5d9708160cdd8ed3d14eaf45df..c81b8a7611ddbc33309ab908490932c32e542c1c 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.rule.index.RuleQuery;
 import org.sonar.server.ws.TestRequest;
@@ -54,6 +55,7 @@ import static org.sonar.server.rule.ws.SearchAction.defineRuleSearchParameters;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES;
@@ -72,7 +74,7 @@ public class RuleQueryFactoryTest {
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
   @Rule
-  public ExpectedException thrown = ExpectedException.none();
+  public ExpectedException expectedException = ExpectedException.none();
 
   private DbClient dbClient = dbTester.getDbClient();
 
@@ -108,13 +110,14 @@ public class RuleQueryFactoryTest {
     assertThat(result.getTags()).isNull();
     assertThat(result.templateKey()).isNull();
     assertThat(result.getTypes()).isEmpty();
-
     assertThat(result.getSortField()).isNull();
+    assertThat(result.getCompareToQProfile()).isNull();
   }
 
   @Test
   public void create_query() throws Exception {
     QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
+    QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(organization);
 
     RuleQuery result = execute(
       PARAM_RULE_KEY, "ruleKey",
@@ -126,8 +129,9 @@ public class RuleQueryFactoryTest {
       PARAM_IS_TEMPLATE, "true",
       PARAM_LANGUAGES, "java,js",
       TEXT_QUERY, "S001",
-      PARAM_QPROFILE, qualityProfile.getKee(),
       PARAM_ORGANIZATION, organization.getKey(),
+      PARAM_QPROFILE, qualityProfile.getKee(),
+      PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(),
       PARAM_REPOSITORIES, "pmd,checkstyle",
       PARAM_SEVERITIES, "MINOR,CRITICAL",
       PARAM_STATUSES, "DEPRECATED,READY",
@@ -149,6 +153,7 @@ public class RuleQueryFactoryTest {
     assertThat(result.getLanguages()).containsOnly(qualityProfile.getLanguage());
     assertThat(result.getQueryText()).isEqualTo("S001");
     assertThat(result.getQProfile().getKee()).isEqualTo(qualityProfile.getKee());
+    assertThat(result.getCompareToQProfile().getKee()).isEqualTo(compareToQualityProfile.getKee());
     assertThat(result.getOrganization().getUuid()).isEqualTo(organization.getUuid());
     assertThat(result.getRepositories()).containsOnly("pmd", "checkstyle");
     assertThat(result.getRuleKey()).isNull();
@@ -205,6 +210,17 @@ public class RuleQueryFactoryTest {
     assertThat(result.getOrganization().getUuid()).isEqualTo(organization.getUuid());
   }
 
+  @Test
+  public void filter_on_compare_to() {
+    QProfileDto compareToProfile = dbTester.qualityProfiles().insert(organization);
+
+    RuleQuery result = execute(
+      PARAM_ORGANIZATION, organization.getKey(),
+      PARAM_COMPARE_TO_PROFILE, compareToProfile.getKee());
+
+    assertThat(result.getCompareToQProfile().getKee()).isEqualTo(compareToProfile.getKee());
+  }
+
   @Test
   public void fail_if_organization_and_quality_profile_are_contradictory() throws Exception {
     OrganizationDto organization1 = dbTester.organizations().insert();
@@ -215,13 +231,60 @@ public class RuleQueryFactoryTest {
     String qualityProfileKey = qualityProfile.getKee();
     String organization2Key = organization2.getKey();
 
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("The specified quality profile '" + qualityProfileKey + "' is not part of the specified organization '" + organization2Key + "'");
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The specified quality profile '" + qualityProfileKey + "' is not part of the specified organization '" + organization2Key + "'");
 
     execute(PARAM_QPROFILE, qualityProfileKey,
       PARAM_ORGANIZATION, organization2Key);
   }
 
+  @Test
+  public void fail_if_organization_and_compare_to_quality_profile_are_contradictory() throws Exception {
+    OrganizationDto organization = dbTester.organizations().insert();
+    QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
+
+    OrganizationDto otherOrganization = dbTester.organizations().insert();
+    QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(otherOrganization);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The specified quality profile '" + compareToQualityProfile.getKee() + "' is not part of the specified organization '" + organization.getKey() + "'");
+
+    execute(PARAM_QPROFILE, qualityProfile.getKee(),
+      PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(),
+      PARAM_ORGANIZATION, organization.getKey());
+  }
+
+  @Test
+  public void fail_when_organization_does_not_exist() {
+    QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
+    String qualityProfileKey = qualityProfile.getKee();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("No organization with key 'unknown'");
+
+    execute(PARAM_QPROFILE, qualityProfileKey,
+      PARAM_ORGANIZATION, "unknown");
+  }
+
+  @Test
+  public void fail_when_profile_does_not_exist() {
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("The specified qualityProfile 'unknown' does not exist");
+
+    execute(PARAM_QPROFILE, "unknown");
+  }
+
+  @Test
+  public void fail_when_compare_to_profile_does_not_exist() {
+    QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("The specified qualityProfile 'unknown' does not exist");
+
+    execute(PARAM_QPROFILE, qualityProfile.getKee(),
+      PARAM_COMPARE_TO_PROFILE, "unknown");
+  }
+
   private RuleQuery execute(String... paramsKeyAndValue) {
     WsActionTester ws = new WsActionTester(fakeAction);
     TestRequest request = ws.newRequest();
index 1d5507795ead1ae3daec00b17bafab0c62d60e64..4f9108f22566bf7f54a0cf43077708861d12538f 100644 (file)
@@ -82,36 +82,42 @@ import static org.mockito.Mockito.mock;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.db.rule.RuleTesting.setSystemTags;
 import static org.sonar.db.rule.RuleTesting.setTags;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ORGANIZATION;
+import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_QPROFILE;
 import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_RULE_KEY;
 
 public class SearchActionTest {
 
+  private static final String JAVA = "java";
+
   @org.junit.Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
   @org.junit.Rule
-  public ExpectedException thrown = ExpectedException.none();
+  public ExpectedException expectedException = ExpectedException.none();
 
   private System2 system2 = new AlwaysIncreasingSystem2();
   @org.junit.Rule
-  public DbTester dbTester = DbTester.create(system2);
+  public DbTester db = DbTester.create(system2);
   @org.junit.Rule
   public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings()));
 
-  private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester);
+  private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
   private RuleIndex ruleIndex = new RuleIndex(es.client());
-  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient());
-  private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbTester.getDbClient(), es.client(), new ActiveRuleIteratorFactory(dbTester.getDbClient()));
-  private Languages languages = LanguageTesting.newLanguages("java", "js");
-  private ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(dbTester.getDbClient(), languages);
-  private RuleWsSupport wsSupport = new RuleWsSupport(dbTester.getDbClient(), userSession, defaultOrganizationProvider);
-  private RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(dbTester.getDbClient(), wsSupport);
+  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client(), new ActiveRuleIteratorFactory(db.getDbClient()));
+  private Languages languages = LanguageTesting.newLanguages(JAVA, "js");
+  private ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(db.getDbClient(), languages);
+  private RuleWsSupport wsSupport = new RuleWsSupport(db.getDbClient(), userSession, defaultOrganizationProvider);
+  private RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient(), wsSupport);
   private MacroInterpreter macroInterpreter = mock(MacroInterpreter.class);
   private RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter);
-  private SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, dbTester.getDbClient(), ruleMapper);
+  private SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, db.getDbClient(), ruleMapper);
 
-  private RuleActivatorContextFactory contextFactory = new RuleActivatorContextFactory(dbTester.getDbClient());
+  private RuleActivatorContextFactory contextFactory = new RuleActivatorContextFactory(db.getDbClient());
   private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
-  private RuleActivator ruleActivator = new RuleActivator(system2, dbTester.getDbClient(), ruleIndex, contextFactory, typeValidations, activeRuleIndexer,
+  private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), ruleIndex, contextFactory, typeValidations, activeRuleIndexer,
     userSession);
   private WsActionTester ws = new WsActionTester(underTest);
 
@@ -122,10 +128,19 @@ public class SearchActionTest {
 
   @Test
   public void test_definition() {
-    assertThat(ws.getDef().isPost()).isFalse();
-    assertThat(ws.getDef().since()).isEqualTo("4.4");
-    assertThat(ws.getDef().isInternal()).isFalse();
-    assertThat(ws.getDef().params()).hasSize(22);
+    WebService.Action def = ws.getDef();
+
+    assertThat(def.isPost()).isFalse();
+    assertThat(def.since()).isEqualTo("4.4");
+    assertThat(def.isInternal()).isFalse();
+    assertThat(def.responseExampleAsString()).isNotEmpty();
+    assertThat(def.params()).hasSize(23);
+
+    WebService.Param compareToProfile = def.param("compareToProfile");
+    assertThat(compareToProfile.since()).isEqualTo("6.5");
+    assertThat(compareToProfile.isRequired()).isFalse();
+    assertThat(compareToProfile.isInternal()).isTrue();
+    assertThat(compareToProfile.description()).isEqualTo("Quality profile key to filter rules that are activated. Meant to compare easily to profile set in 'qprofile'");
   }
 
   @Test
@@ -208,7 +223,7 @@ public class SearchActionTest {
 
   @Test
   public void should_filter_on_organization_specific_tags() throws IOException {
-    OrganizationDto organization = dbTester.organizations().insert();
+    OrganizationDto organization = db.organizations().insert();
     RuleDefinitionDto rule1 = createJavaRule();
     RuleMetadataDto metadata1 = insertMetadata(organization, rule1, setTags("tag1", "tag2"));
     RuleDefinitionDto rule2 = createJavaRule();
@@ -244,8 +259,8 @@ public class SearchActionTest {
 
   @Test
   public void should_list_tags_in_tags_facet() throws IOException {
-    OrganizationDto organization = dbTester.organizations().insert();
-    RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag3", "tag5", "tag7", "tag9", "x"));
+    OrganizationDto organization = db.organizations().insert();
+    RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag3", "tag5", "tag7", "tag9", "x"));
     RuleMetadataDto metadata = insertMetadata(organization, rule, setTags("tag2", "tag4", "tag6", "tag8", "tagA"));
     indexRules();
 
@@ -260,7 +275,7 @@ public class SearchActionTest {
 
   @Test
   public void should_include_selected_matching_tag_in_facet() throws IOException {
-    RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA", "x"));
+    RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA", "x"));
     indexRules();
 
     SearchResponse result = ws.newRequest()
@@ -272,7 +287,7 @@ public class SearchActionTest {
 
   @Test
   public void should_included_selected_non_matching_tag_in_facet() throws IOException {
-    RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA"));
+    RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA"));
     indexRules();
 
     SearchResponse result = ws.newRequest()
@@ -284,7 +299,7 @@ public class SearchActionTest {
 
   @Test
   public void should_return_organization_specific_tags() throws IOException {
-    OrganizationDto organization = dbTester.organizations().insert();
+    OrganizationDto organization = db.organizations().insert();
     RuleDefinitionDto rule = createJavaRule();
     RuleMetadataDto metadata = insertMetadata(organization, rule, setTags("tag1", "tag2"));
     indexRules();
@@ -344,9 +359,9 @@ public class SearchActionTest {
 
   @Test
   public void return_lang_key_field_when_language_name_is_not_available() throws Exception {
-    OrganizationDto organization = dbTester.organizations().insert();
+    OrganizationDto organization = db.organizations().insert();
     String unknownLanguage = "unknown_" + randomAlphanumeric(5);
-    RuleDefinitionDto rule = dbTester.rules().insert(r -> r.setLanguage(unknownLanguage));
+    RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage(unknownLanguage));
 
     indexRules();
 
@@ -364,13 +379,13 @@ public class SearchActionTest {
 
   @Test
   public void search_debt_rules_with_default_and_overridden_debt_values() throws Exception {
-    RuleDefinitionDto rule = dbTester.rules().insert(r ->
+    RuleDefinitionDto rule = db.rules().insert(r ->
       r.setLanguage("java")
         .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
         .setDefRemediationGapMultiplier("1h")
         .setDefRemediationBaseEffort("15min"));
 
-    RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule,
+    RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule,
       r -> r.setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
         .setRemediationGapMultiplier("2h")
         .setRemediationBaseEffort("25min"));
@@ -399,13 +414,13 @@ public class SearchActionTest {
 
   @Test
   public void search_debt_rules_with_default_linear_offset_and_overridden_constant_debt() throws Exception {
-    RuleDefinitionDto rule = dbTester.rules().insert(r ->
+    RuleDefinitionDto rule = db.rules().insert(r ->
       r.setLanguage("java")
         .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
         .setDefRemediationGapMultiplier("1h")
         .setDefRemediationBaseEffort("15min"));
 
-    RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule,
+    RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule,
       r -> r.setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name())
         .setRemediationGapMultiplier(null)
         .setRemediationBaseEffort("5min"));
@@ -435,13 +450,13 @@ public class SearchActionTest {
 
   @Test
   public void search_debt_rules_with_default_linear_offset_and_overridden_linear_debt() throws Exception {
-    RuleDefinitionDto rule = dbTester.rules().insert(r ->
+    RuleDefinitionDto rule = db.rules().insert(r ->
       r.setLanguage("java")
         .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
         .setDefRemediationGapMultiplier("1h")
         .setDefRemediationBaseEffort("15min"));
 
-    RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule,
+    RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule,
       r -> r.setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name())
         .setRemediationGapMultiplier("1h")
         .setRemediationBaseEffort(null));
@@ -470,10 +485,10 @@ public class SearchActionTest {
 
   @Test
   public void search_template_rules() throws Exception {
-    RuleDefinitionDto templateRule = dbTester.rules().insert(r ->
+    RuleDefinitionDto templateRule = db.rules().insert(r ->
       r.setLanguage("java")
         .setIsTemplate(true));
-    RuleDefinitionDto rule = dbTester.rules().insert(r ->
+    RuleDefinitionDto rule = db.rules().insert(r ->
       r.setLanguage("java")
         .setTemplateId(templateRule.getId()));
 
@@ -494,10 +509,10 @@ public class SearchActionTest {
 
   @Test
   public void search_custom_rules_from_template_key() throws Exception {
-    RuleDefinitionDto templateRule = dbTester.rules().insert(r ->
+    RuleDefinitionDto templateRule = db.rules().insert(r ->
       r.setLanguage("java")
         .setIsTemplate(true));
-    RuleDefinitionDto rule = dbTester.rules().insert(r ->
+    RuleDefinitionDto rule = db.rules().insert(r ->
       r.setLanguage("java")
         .setTemplateId(templateRule.getId()));
 
@@ -518,11 +533,11 @@ public class SearchActionTest {
 
   @Test
   public void search_all_active_rules() throws Exception {
-    OrganizationDto organization = dbTester.organizations().insert();
-    QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
     RuleDefinitionDto rule = createJavaRule();
     RuleActivation activation = RuleActivation.create(rule.getKey(), BLOCKER, null);
-    ruleActivator.activate(dbTester.getSession(), activation, profile);
+    ruleActivator.activate(db.getSession(), activation, profile);
 
     indexRules();
 
@@ -543,38 +558,38 @@ public class SearchActionTest {
 
   @Test
   public void search_profile_active_rules() throws Exception {
-    OrganizationDto organization = dbTester.organizations().insert();
-    QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
-    QProfileDto waterproofProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
+    QProfileDto waterproofProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
 
     RuleDefinitionDto rule = createJavaRule();
 
-    RuleParamDto ruleParam1 = dbTester.rules().insertRuleParam(rule, p ->
+    RuleParamDto ruleParam1 = db.rules().insertRuleParam(rule, p ->
       p.setDefaultValue("some value")
         .setType("STRING")
         .setDescription("My small description")
         .setName("my_var"));
 
-    RuleParamDto ruleParam2 = dbTester.rules().insertRuleParam(rule, p ->
+    RuleParamDto ruleParam2 = db.rules().insertRuleParam(rule, p ->
       p.setDefaultValue("1")
         .setType("INTEGER")
         .setDescription("My small description")
         .setName("the_var"));
 
     // SONAR-7083
-    RuleParamDto ruleParam3 = dbTester.rules().insertRuleParam(rule, p ->
+    RuleParamDto ruleParam3 = db.rules().insertRuleParam(rule, p ->
       p.setDefaultValue(null)
         .setType("STRING")
         .setDescription("Empty Param")
         .setName("empty_var"));
 
     RuleActivation activation = RuleActivation.create(rule.getKey());
-    List<ActiveRuleChange> activeRuleChanges1 = ruleActivator.activate(dbTester.getSession(), activation, profile);
-    ruleActivator.activate(dbTester.getSession(), activation, waterproofProfile);
+    List<ActiveRuleChange> activeRuleChanges1 = ruleActivator.activate(db.getSession(), activation, profile);
+    ruleActivator.activate(db.getSession(), activation, waterproofProfile);
 
     assertThat(activeRuleChanges1).hasSize(1);
 
-    dbTester.commit();
+    db.commit();
 
     indexRules();
     indexActiveRules();
@@ -602,8 +617,8 @@ public class SearchActionTest {
     );
 
     String unknownProfile = "unknown_profile" + randomAlphanumeric(5);
-    thrown.expect(NotFoundException.class);
-    thrown.expectMessage("The specified qualityProfile '" + unknownProfile + "' does not exist");
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("The specified qualityProfile '" + unknownProfile + "' does not exist");
 
     ws.newRequest()
       .setParam("activation", "true")
@@ -612,26 +627,26 @@ public class SearchActionTest {
   }
 
   @Test
-  public void test_SONAR7083() {
-    OrganizationDto organization = dbTester.organizations().insert();
-    QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
+  public void search_for_active_rules_when_parameter_value_is_null() {
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java"));
 
     RuleDefinitionDto rule = createJavaRule();
 
-    RuleParamDto ruleParam = dbTester.rules().insertRuleParam(rule, p ->
+    RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p ->
       p.setDefaultValue("some value")
         .setType("STRING")
         .setDescription("My small description")
         .setName("my_var"));
 
     RuleActivation activation = RuleActivation.create(rule.getKey());
-    List<ActiveRuleChange> activeRuleChanges = ruleActivator.activate(dbTester.getSession(), activation, profile);
+    List<ActiveRuleChange> activeRuleChanges = ruleActivator.activate(db.getSession(), activation, profile);
 
     // Insert directly in database a rule parameter with a null value
     ActiveRuleParamDto activeRuleParam = ActiveRuleParamDto.createFor(ruleParam).setValue(null);
-    dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleChanges.get(0).getActiveRule(), activeRuleParam);
+    db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleChanges.get(0).getActiveRule(), activeRuleParam);
 
-    dbTester.commit();
+    db.commit();
 
     indexRules();
     indexActiveRules();
@@ -660,9 +675,9 @@ public class SearchActionTest {
 
   @Test
   public void statuses_facet_should_be_sticky() throws Exception {
-    RuleDefinitionDto rule1 = dbTester.rules().insert(r -> r.setLanguage("java"));
-    RuleDefinitionDto rule2 = dbTester.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.BETA));
-    RuleDefinitionDto rule3 = dbTester.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.DEPRECATED));
+    RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("java"));
+    RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.BETA));
+    RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.DEPRECATED));
 
     indexRules();
 
@@ -679,6 +694,39 @@ public class SearchActionTest {
     );
   }
 
+  @Test
+  public void compare_to_another_profile() throws Exception {
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(JAVA));
+    QProfileDto anotherProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage(JAVA));
+    RuleDefinitionDto commonRule = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    RuleDefinitionDto profileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    RuleDefinitionDto profileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    RuleDefinitionDto profileRule3 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    RuleDefinitionDto anotherProfileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    RuleDefinitionDto anotherProfileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition();
+    db.qualityProfiles().activateRule(profile, commonRule);
+    db.qualityProfiles().activateRule(profile, profileRule1);
+    db.qualityProfiles().activateRule(profile, profileRule2);
+    db.qualityProfiles().activateRule(profile, profileRule3);
+    db.qualityProfiles().activateRule(anotherProfile, commonRule);
+    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule1);
+    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2);
+    indexRules();
+    indexActiveRules();
+
+    SearchResponse result = ws.newRequest()
+      .setParam(PARAM_ORGANIZATION, organization.getKey())
+      .setParam(PARAM_QPROFILE, profile.getKee())
+      .setParam(PARAM_ACTIVATION, "false")
+      .setParam(PARAM_COMPARE_TO_PROFILE, anotherProfile.getKee())
+      .executeProtobuf(SearchResponse.class);
+
+    assertThat(result.getRulesList())
+      .extracting(Rule::getKey)
+      .containsExactlyInAnyOrder(anotherProfileRule1.getKey().toString(), anotherProfileRule2.getKey().toString());
+  }
+
   @SafeVarargs
   private final <T> void checkField(RuleDefinitionDto rule, String fieldName, Extractor<Rule, T> responseExtractor, T... expected) throws IOException {
     SearchResponse result = ws.newRequest()
@@ -690,7 +738,7 @@ public class SearchActionTest {
 
   @SafeVarargs
   private final RuleMetadataDto insertMetadata(OrganizationDto organization, RuleDefinitionDto rule, Consumer<RuleMetadataDto>... populaters) {
-    RuleMetadataDto metadata = dbTester.rules().insertOrUpdateMetadata(rule, organization, populaters);
+    RuleMetadataDto metadata = db.rules().insertOrUpdateMetadata(rule, organization, populaters);
     ruleIndexer.indexRuleExtension(organization, rule.getKey());
     return metadata;
   }
@@ -723,6 +771,6 @@ public class SearchActionTest {
   }
 
   private RuleDefinitionDto createJavaRule() {
-    return dbTester.rules().insert(r -> r.setLanguage("java"));
+    return db.rules().insert(r -> r.setLanguage("java"));
   }
 }
index 0024d620430dc8b61c30699c97e03c6447f63a0b..0cbe4dc7e70e26c6124b481c962253ac507689b0 100644 (file)
@@ -38,6 +38,7 @@ public class RulesWsParameters {
   public static final String PARAM_IS_TEMPLATE = "is_template";
   public static final String PARAM_TEMPLATE_KEY = "template_key";
   public static final String PARAM_ORGANIZATION = "organization";
+  public static final String PARAM_COMPARE_TO_PROFILE = "compare_to_profile";
 
   public static final String FIELD_REPO = "repo";
   public static final String FIELD_NAME = "name";