]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10575 Index and search external rules
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 12 Apr 2018 13:35:53 +0000 (15:35 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 26 Apr 2018 18:20:50 +0000 (20:20 +0200)
28 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleTesting.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryImpl.java
server/sonar-server/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java
server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleDoc.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleQuery.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.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-show.json
server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ExternalRuleCreatorTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java
sonar-ws/src/main/protobuf/ws-rules.proto

index 58c0c97ec50c3308555468953ae8efa99359ae02..f062fee4298ead5c3cdf2d5bb9c3ef4beb8c7373 100644 (file)
@@ -146,8 +146,10 @@ import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
 import org.sonar.server.rule.CommonRuleDefinitionsImpl;
 import org.sonar.server.rule.DefaultRuleFinder;
 import org.sonar.server.rule.DeprecatedRulesDefinitionLoader;
+import org.sonar.server.rule.ExternalRuleCreator;
 import org.sonar.server.rule.RuleDefinitionsLoader;
 import org.sonar.server.rule.index.RuleIndex;
+import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.setting.DatabaseSettingLoader;
 import org.sonar.server.setting.DatabaseSettingsEnabler;
 import org.sonar.server.setting.ThreadLocalSettings;
@@ -343,6 +345,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       CommonRuleDefinitionsImpl.class,
       RuleDefinitionsLoader.class,
       RulesDefinitionXmlLoader.class,
+      ExternalRuleCreator.class,
+      RuleIndexer.class,
 
       // languages
       Languages.class, // used by CommonRuleDefinitionsImpl
index 158f9ebe545e17358dde6b9d3585ff89cdbb80f3..af333c9683de84f40e4b3c0210e0d89de7e4e307 100644 (file)
@@ -93,7 +93,7 @@ public class ComputeEngineContainerImplTest {
     assertThat(picoContainer.getComponentAdapters())
       .hasSize(
         CONTAINER_ITSELF
-          + 81 // level 4
+          + 83 // level 4
           + 21 // content of QualityGateModule
           + 6 // content of CeConfigurationModule
           + 4 // content of CeQueueModule
index 38075e93fc5bb35bfb67d32a67c373e7485e6e9d..6d5d64c9eca0ef31709e9f6a3822781583940dc9 100644 (file)
@@ -181,6 +181,11 @@ public class RuleDto {
     return definition.isExternal();
   }
 
+  public RuleDto setIsExternal(boolean isExternal) {
+    definition.setIsExternal(isExternal);
+    return this;
+  }
+
   public boolean isTemplate() {
     return definition.isTemplate();
   }
index 9840e9207d827e0ab00500ab0655679207fa8ece..0a42c35461367caca00f945e28d4e3c53b412b56 100644 (file)
@@ -42,6 +42,7 @@ public class RuleForIndexingDto {
   private String templateRepository;
   private String internalKey;
   private String language;
+  private boolean isExternal;
   private int type;
   private long createdAt;
   private long updatedAt;
@@ -107,6 +108,10 @@ public class RuleForIndexingDto {
   public int getType() {
     return type;
   }
+  
+  public boolean isExternal() {
+    return isExternal;
+  }
 
   public long getCreatedAt() {
     return createdAt;
index 0d5acbf4bba4d85c3a23bcfc14c8bf2bc35cf4fb..3aa294ff91d867d39f8b3985e13606c10dbb88f9 100644 (file)
       r.priority as "severity",
       r.status as "status",
       r.is_template as "isTemplate",
+      r.is_external as "isExternal",
       r.system_tags as "systemTags",
       t.plugin_rule_key as "templateRuleKey",
       t.plugin_name as "templateRepository",
index 44e469219f9dabe73f01f4a3de8b32c5386e514f..99d2e4555c482b72925faf4256aeb6aa6e11ecd4 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -360,6 +361,7 @@ public class RuleDaoTest {
     assertThat(actual.getConfigKey()).isEqualTo(expected.getConfigKey());
     assertThat(actual.getSeverity()).isEqualTo(expected.getSeverity());
     assertThat(actual.getSeverityString()).isEqualTo(expected.getSeverityString());
+    assertThat(actual.isExternal()).isEqualTo(expected.isExternal());
     assertThat(actual.isTemplate()).isEqualTo(expected.isTemplate());
     assertThat(actual.getLanguage()).isEqualTo(expected.getLanguage());
     assertThat(actual.getTemplateId()).isEqualTo(expected.getTemplateId());
@@ -809,14 +811,15 @@ public class RuleDaoTest {
   public void scrollIndexingRules() {
     Accumulator<RuleForIndexingDto> accumulator = new Accumulator<>();
     RuleDefinitionDto r1 = db.rules().insert();
-    RuleDefinitionDto r2 = db.rules().insert();
+    RuleDefinitionDto r2 = db.rules().insert(r -> r.setIsExternal(true));
 
     underTest.scrollIndexingRules(db.getSession(), accumulator);
 
     assertThat(accumulator.list)
       .extracting(RuleForIndexingDto::getId, RuleForIndexingDto::getRuleKey)
       .containsExactlyInAnyOrder(tuple(r1.getId(), r1.getKey()), tuple(r2.getId(), r2.getKey()));
-    RuleForIndexingDto firstRule = accumulator.list.iterator().next();
+    Iterator<RuleForIndexingDto> it = accumulator.list.iterator();
+    RuleForIndexingDto firstRule = it.next();
 
     assertThat(firstRule.getRepository()).isEqualTo(r1.getRepositoryKey());
     assertThat(firstRule.getPluginRuleKey()).isEqualTo(r1.getRuleKey());
@@ -825,6 +828,7 @@ public class RuleDaoTest {
     assertThat(firstRule.getDescriptionFormat()).isEqualTo(r1.getDescriptionFormat());
     assertThat(firstRule.getSeverity()).isEqualTo(r1.getSeverity());
     assertThat(firstRule.getStatus()).isEqualTo(r1.getStatus());
+    assertThat(firstRule.isExternal()).isFalse();
     assertThat(firstRule.isTemplate()).isEqualTo(r1.isTemplate());
     assertThat(firstRule.getSystemTagsAsSet()).isEqualTo(r1.getSystemTags());
     assertThat(firstRule.getTemplateRuleKey()).isNull();
@@ -834,6 +838,10 @@ public class RuleDaoTest {
     assertThat(firstRule.getType()).isEqualTo(r1.getType());
     assertThat(firstRule.getCreatedAt()).isEqualTo(r1.getCreatedAt());
     assertThat(firstRule.getUpdatedAt()).isEqualTo(r1.getUpdatedAt());
+    
+    RuleForIndexingDto secondRule = it.next();
+    assertThat(secondRule.isExternal()).isTrue();
+    
   }
 
   @Test
index 3339cc08120ee674ae1a1032b8240ebdaa7e60e4..77b70a3ecffde54487fc6fdb14e6abbed4eb4cc2 100644 (file)
@@ -305,6 +305,10 @@ public class RuleTesting {
   public static Consumer<RuleDefinitionDto> setType(RuleType type) {
     return rule -> rule.setType(type);
   }
+  
+  public static Consumer<RuleDefinitionDto> setIsExternal(boolean isExternal) {
+    return rule -> rule.setIsExternal(isExternal);
+  }
 
   public static Consumer<RuleDefinitionDto> setIsTemplate(boolean isTemplate) {
     return rule -> rule.setIsTemplate(isTemplate);
index b7692f5cb56a48755e6f9c1309e6504ec1d501ee..f40d4dece13806e9afecc54d7c4bd2b49268a180 100644 (file)
@@ -122,7 +122,6 @@ import org.sonar.server.computation.task.projectanalysis.webhook.WebhookPostTask
 import org.sonar.server.computation.task.step.ComputationStepExecutor;
 import org.sonar.server.computation.task.step.ComputationSteps;
 import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl;
-import org.sonar.server.rule.ExternalRuleCreator;
 import org.sonar.server.view.index.ViewIndex;
 
 public final class ProjectAnalysisTaskContainerPopulator implements ContainerPopulator<TaskContainer> {
@@ -197,7 +196,6 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
       DuplicationRepositoryImpl.class,
 
       // issues
-      ExternalRuleCreator.class,
       RuleRepositoryImpl.class,
       ScmAccountToUserLoader.class,
       ScmAccountToUser.class,
index 10eb88f7e09f093fc8d288c5401cf1c18ff18320..b29dea960cde10d5a9f2287c22134d5ce29439d7 100644 (file)
@@ -72,7 +72,7 @@ public class RuleRepositoryImpl implements RuleRepository {
   }
 
   private void persistAndIndex(DbSession dbSession, NewExternalRule external) {
-    Rule rule = creator.create(dbSession, external);
+    Rule rule = creator.persistAndIndex(dbSession, external);
     rulesById.put(rule.getId(), rule);
     rulesByKey.put(external.getKey(), rule);
   }
index 2cbef97cbd5c771c863690460bde72d0731f4d5a..31f4467ff1840c4a0d6d424ec2a5f07df32e4521 100644 (file)
@@ -19,7 +19,7 @@
  */
 package org.sonar.server.rule;
 
-import org.sonar.api.server.ServerSide;
+import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -28,34 +28,45 @@ import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.server.computation.task.projectanalysis.issue.NewExternalRule;
 import org.sonar.server.computation.task.projectanalysis.issue.Rule;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleImpl;
+import org.sonar.server.rule.index.RuleIndexer;
 
 import static org.sonar.db.rule.RuleDto.Scope.ALL;
 
-@ServerSide
 public class ExternalRuleCreator {
 
   private final DbClient dbClient;
   private final System2 system2;
+  private final RuleIndexer ruleIndexer;
 
-  public ExternalRuleCreator(DbClient dbClient, System2 system2) {
+  public ExternalRuleCreator(DbClient dbClient, System2 system2, RuleIndexer ruleIndexer) {
     this.dbClient = dbClient;
     this.system2 = system2;
+    this.ruleIndexer = ruleIndexer;
   }
 
-  public Rule create(DbSession dbSession, NewExternalRule external) {
+  /**
+   * Persists a rule in the DB and indexes it.
+   * @return the rule that was inserted in the DB, which <b>includes the generated ID</b>. 
+   */
+  public Rule persistAndIndex(DbSession dbSession, NewExternalRule external) {
     RuleDao dao = dbClient.ruleDao();
     dao.insert(dbSession, new RuleDefinitionDto()
       .setRuleKey(external.getKey())
       .setPluginKey(external.getPluginKey())
-      .setIsExternal(true)
+      .setIsExternal(external.isExternal())
       .setName(external.getName())
       .setDescriptionURL(external.getDescriptionUrl())
       .setType(external.getType())
       .setScope(ALL)
+      .setStatus(RuleStatus.READY)
       .setSeverity(external.getSeverity())
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now()));
-    return new RuleImpl(dao.selectOrFailByKey(dbSession, external.getKey()));
+
+    Rule newRule = new RuleImpl(dao.selectOrFailByKey(dbSession, external.getKey()));
+    // TODO write rule repository if needed
+    ruleIndexer.commitAndIndex(dbSession, newRule.getId());
+    return newRule;
   }
 
 }
index 3ffb85b521f54e24b585e529f391c9e3ecf6f80d..9f9d1ce37640962580ae6063b4e446223b846d8c 100644 (file)
@@ -391,6 +391,7 @@ public class RegisterRules implements Startable {
       .setSystemTags(ruleDef.tags())
       .setType(RuleType.valueOf(ruleDef.type().name()))
       .setScope(toDtoScope(ruleDef.scope()))
+      .setIsExternal(false)
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now());
     if (ruleDef.htmlDescription() != null) {
@@ -633,7 +634,7 @@ public class RegisterRules implements Startable {
     recorder.getRemaining().forEach(rule -> {
       if (rule.isCustomRule()) {
         customRules.add(rule);
-      } else if (rule.getStatus() != RuleStatus.REMOVED) {
+      } else if (!rule.isExternal() && rule.getStatus() != RuleStatus.REMOVED) {
         removeRule(dbSession, recorder, rule);
       }
     });
index 4645f7ce6d8041cd1ff33312a7a129a6c1eb2f91..f413798dc893f8f083d93855700fc848c2a72b74 100644 (file)
@@ -181,6 +181,15 @@ public class RuleDoc extends BaseDoc {
     return this;
   }
 
+  public boolean isExternal() {
+    return getField(RuleIndexDefinition.FIELD_RULE_IS_EXTERNAL);
+  }
+
+  public RuleDoc setIsExternal(boolean b) {
+    setField(RuleIndexDefinition.FIELD_RULE_IS_EXTERNAL, b);
+    return this;
+  }
+
   public RuleType type() {
     return RuleType.valueOf(getField(RuleIndexDefinition.FIELD_RULE_TYPE));
   }
@@ -220,6 +229,7 @@ public class RuleDoc extends BaseDoc {
       .setRepository(dto.getRepository())
       .setInternalKey(dto.getInternalKey())
       .setIsTemplate(dto.isTemplate())
+      .setIsExternal(dto.isExternal())
       .setLanguage(dto.getLanguage())
       .setName(dto.getName())
       .setRuleKey(dto.getPluginRuleKey())
index 25f98b5098f868e95078c05974e4976e1c4866c1..d93d6fc5893d2a745b5da37543bfc09e148fce30 100644 (file)
@@ -93,6 +93,7 @@ import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_EXTENSI
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_EXTENSION_TAGS;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_INTERNAL_KEY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IS_EXTERNAL;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IS_TEMPLATE;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_LANGUAGE;
@@ -205,13 +206,12 @@ public class RuleIndex {
         .stream().map(token -> boolQuery().should(
           matchQuery(
             SEARCH_GRAMS_ANALYZER.subField(FIELD_RULE_NAME),
-            StringUtils.left(token, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH)
-          ).boost(20f)).should(
-          matchPhraseQuery(
-            ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION),
-            token
-          ).boost(3f))
-      ).forEach(textQuery::must);
+            StringUtils.left(token, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH)).boost(20f))
+          .should(
+            matchPhraseQuery(
+              ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION),
+              token).boost(3f)))
+        .forEach(textQuery::must);
       qb.should(textQuery.boost(20f));
     }
 
@@ -302,6 +302,12 @@ public class RuleIndex {
         QueryBuilders.termQuery(FIELD_RULE_IS_TEMPLATE, Boolean.toString(isTemplate)));
     }
 
+    Boolean isExternal = query.isExternal();
+    if (isExternal != null) {
+      filters.put(FIELD_RULE_IS_EXTERNAL,
+        QueryBuilders.termQuery(FIELD_RULE_IS_EXTERNAL, Boolean.toString(isExternal)));
+    }
+
     String template = query.templateKey();
     if (template != null) {
       filters.put(FIELD_RULE_TEMPLATE_KEY,
index 38e54fcb4b0edfd878d3eeca5ded53471fe51292..63567d83026fca9748f0626fc2a08f764fc6b1a9 100644 (file)
@@ -53,6 +53,7 @@ public class RuleIndexDefinition implements IndexDefinition {
   public static final String FIELD_RULE_STATUS = "status";
   public static final String FIELD_RULE_LANGUAGE = "lang";
   public static final String FIELD_RULE_IS_TEMPLATE = "isTemplate";
+  public static final String FIELD_RULE_IS_EXTERNAL = "isExternal";
   public static final String FIELD_RULE_TEMPLATE_KEY = "templateKey";
   public static final String FIELD_RULE_TYPE = "type";
   public static final String FIELD_RULE_CREATED_AT = "createdAt";
@@ -154,6 +155,7 @@ public class RuleIndexDefinition implements IndexDefinition {
     ruleMapping.keywordFieldBuilder(FIELD_RULE_LANGUAGE).disableNorms().build();
 
     ruleMapping.createBooleanField(FIELD_RULE_IS_TEMPLATE);
+    ruleMapping.createBooleanField(FIELD_RULE_IS_EXTERNAL);
     ruleMapping.keywordFieldBuilder(FIELD_RULE_TEMPLATE_KEY).disableNorms().build();
 
     ruleMapping.keywordFieldBuilder(FIELD_RULE_TYPE).disableNorms().build();
index e0b6f3040a4d9d22b6e57b3385676c22470afb99..4b79ffcfcd9159e1548b3a95d8efb483d9ab0984 100644 (file)
@@ -54,6 +54,7 @@ public class RuleQuery {
   private String internalKey;
   private String ruleKey;
   private OrganizationDto organization;
+  private Boolean isExternal;
 
   @CheckForNull
   public QProfileDto getQProfile() {
@@ -205,6 +206,16 @@ public class RuleQuery {
     return this;
   }
 
+  @CheckForNull
+  public Boolean isExternal() {
+    return isExternal;
+  }
+
+  public RuleQuery setIsExternal(@Nullable Boolean b) {
+    this.isExternal = b;
+    return this;
+  }
+
   @CheckForNull
   public String templateKey() {
     return templateKey;
index 09790900602304baf0760e7aaf406e9852ab790b..9a70c738b52c197a2560fc22a4e6f5909c6dc927 100644 (file)
@@ -52,6 +52,7 @@ import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_EFFORT_TO_FIX_DES
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_GAP_DESCRIPTION;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_HTML_DESCRIPTION;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_INTERNAL_KEY;
+import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_IS_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_LANGUAGE;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_LANGUAGE_NAME;
@@ -115,6 +116,7 @@ public class RuleMapper {
     setLanguage(ruleResponse, ruleDefinitionDto, fieldsToReturn);
     setLanguageName(ruleResponse, ruleDefinitionDto, fieldsToReturn);
     setIsTemplate(ruleResponse, ruleDefinitionDto, fieldsToReturn);
+    setIsExternal(ruleResponse, ruleDefinitionDto, fieldsToReturn);
     setTemplateKey(ruleResponse, ruleDefinitionDto, result, fieldsToReturn);
     setDefaultDebtRemediationFunctionFields(ruleResponse, ruleDefinitionDto, fieldsToReturn);
     setEffortToFixDescription(ruleResponse, ruleDefinitionDto, fieldsToReturn);
@@ -323,6 +325,12 @@ public class RuleMapper {
       ruleResponse.setIsTemplate(ruleDto.isTemplate());
     }
   }
+  
+  private static void setIsExternal(Rules.Rule.Builder ruleResponse, RuleDefinitionDto ruleDto, Set<String> fieldsToReturn) {
+    if (shouldReturnField(fieldsToReturn, FIELD_IS_EXTERNAL)) {
+      ruleResponse.setIsExternal(ruleDto.isExternal());
+    }
+  }
 
   private static void setTemplateKey(Rules.Rule.Builder ruleResponse, RuleDefinitionDto ruleDto, SearchResult result, Set<String> fieldsToReturn) {
     if (shouldReturnField(fieldsToReturn, FIELD_TEMPLATE_KEY) && ruleDto.isCustomRule()) {
index 75baf8a2e4da8a01d1911006312fd7f0d171963d..a197a0ee8b9729e0f76795e898b20f248eec6dd0 100644 (file)
@@ -43,6 +43,7 @@ import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
@@ -91,6 +92,7 @@ public class RuleQueryFactory {
     query.setInheritance(request.paramAsStrings(PARAM_INHERITANCE));
     query.setActiveSeverities(request.paramAsStrings(PARAM_ACTIVE_SEVERITIES));
     query.setIsTemplate(request.paramAsBoolean(PARAM_IS_TEMPLATE));
+    query.setIsExternal(request.paramAsBoolean(PARAM_IS_EXTERNAL));
     query.setTemplateKey(request.param(PARAM_TEMPLATE_KEY));
     query.setTypes(toEnums(request.paramAsStrings(PARAM_TYPES), RuleType.class));
     query.setKey(request.param(PARAM_RULE_KEY));
index 9b87af2679f0eac1db02d9a17eab7372f79f5ce1..06ee60ec50b674bcb7010ed05ed388a6dca5aaa4 100644 (file)
@@ -36,6 +36,7 @@ public class RulesWsParameters {
   public static final String PARAM_INHERITANCE = "inheritance";
   public static final String PARAM_ACTIVE_SEVERITIES = "active_severities";
   public static final String PARAM_IS_TEMPLATE = "is_template";
+  public static final String PARAM_IS_EXTERNAL = "is_external";
   public static final String PARAM_TEMPLATE_KEY = "template_key";
   public static final String PARAM_ORGANIZATION = "organization";
   public static final String PARAM_COMPARE_TO_PROFILE = "compareToProfile";
@@ -46,6 +47,7 @@ public class RulesWsParameters {
   public static final String FIELD_SEVERITY = "severity";
   public static final String FIELD_STATUS = "status";
   public static final String FIELD_INTERNAL_KEY = "internalKey";
+  public static final String FIELD_IS_EXTERNAL = "isExternal";
   public static final String FIELD_IS_TEMPLATE = "isTemplate";
   public static final String FIELD_TEMPLATE_KEY = "templateKey";
   public static final String FIELD_TAGS = "tags";
@@ -90,11 +92,11 @@ public class RulesWsParameters {
    * @since 7.1
    */
   public static final String FIELD_SCOPE = "scope";
-  
+
   public static final String FIELD_PARAMS = "params";
   public static final String FIELD_ACTIVES = "actives";
 
-  public static final Set<String> OPTIONAL_FIELDS = ImmutableSet.of(FIELD_REPO, FIELD_NAME, FIELD_CREATED_AT, FIELD_SEVERITY, FIELD_STATUS, FIELD_INTERNAL_KEY, FIELD_IS_TEMPLATE,
+  public static final Set<String> OPTIONAL_FIELDS = ImmutableSet.of(FIELD_REPO, FIELD_NAME, FIELD_CREATED_AT, FIELD_SEVERITY, FIELD_STATUS, FIELD_INTERNAL_KEY, FIELD_IS_EXTERNAL, FIELD_IS_TEMPLATE,
     FIELD_TEMPLATE_KEY, FIELD_TAGS, FIELD_SYSTEM_TAGS, FIELD_LANGUAGE, FIELD_LANGUAGE_NAME, FIELD_HTML_DESCRIPTION, FIELD_MARKDOWN_DESCRIPTION, FIELD_NOTE_LOGIN,
     FIELD_MARKDOWN_NOTE, FIELD_HTML_NOTE,
     FIELD_DEFAULT_DEBT_REM_FUNCTION, FIELD_EFFORT_TO_FIX_DESCRIPTION, FIELD_DEBT_OVERLOADED, FIELD_DEBT_REM_FUNCTION,
index 856ecf743626b736d1e6791c6acdc71ef02ca113..273498012301d7d1f7b93d31ddc588924212f9a3 100644 (file)
@@ -85,6 +85,7 @@ import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
@@ -132,7 +133,9 @@ public class SearchAction implements RulesWsAction {
       .addPagingParams(100, MAX_LIMIT)
       .setHandler(this)
       .setChangelog(new Change("7.1", "The field 'scope' has been added to the response"))
-      .setChangelog(new Change("7.1", "The field 'scope' has been added to the 'f' parameter"));
+      .setChangelog(new Change("7.1", "The field 'scope' has been added to the 'f' parameter"))
+      .setChangelog(new Change("7.2", "The field 'isExternal' has been added to the response"))
+      .setChangelog(new Change("7.2", "The field 'isExternal' has been added to the 'f' parameter"));;
 
     action.createParam(FACETS)
       .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.")
@@ -292,6 +295,12 @@ public class SearchAction implements RulesWsAction {
       .setDescription("Filter template rules")
       .setBooleanPossibleValues();
 
+    action
+      .createParam(PARAM_IS_EXTERNAL)
+      .setDescription("Filter external engine rules")
+      .setBooleanPossibleValues()
+      .setSince("7.2");
+
     action
       .createParam(PARAM_TEMPLATE_KEY)
       .setDescription("Key of the template rule to filter on. Used to search for the custom rules based on this template.")
index b52d4c8c4c2b381b59769a1d455a1d2eb6fb4c50..8664d1509fa17672a3f14e612083f563040c1b13 100644 (file)
@@ -20,6 +20,7 @@
     "lang": "java",
     "langName": "Java",
     "scope": "MAIN",
+    "isExternal": false,
     "type": "CODE_SMELL",
     "params": [
         {
index 84c840898a841e1864b0d4d37b694eec0bdd0e4e..2e5091ac51f9dfdb9a06bf91c4225ec4e594ae40 100644 (file)
@@ -18,6 +18,7 @@
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
+      "isExternal": false,
       "type": "CODE_SMELL",
       "params": [
         {
@@ -42,6 +43,7 @@
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
+      "isExternal": false,
       "type": "BUG",
       "params": [
         {
@@ -66,6 +68,7 @@
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
+      "isExternal": false,
       "type": "VULNERABILITY",
       "params": [
         {
@@ -93,6 +96,7 @@
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
+      "isExternal": false,
       "type": "CODE_SMELL",
       "params": [
         {
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
+      "isExternal": false,
       "type": "CODE_SMELL",
       "params": [
         {
index 318f4e4ca7baec43505a19a1842d325cf9f68ef6..39d085ad39c5db50ac2b454c43b6665e4a45d5af 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import java.util.List;
 import java.util.Optional;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,6 +42,7 @@ import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetada
 import org.sonar.server.es.EsTester;
 import org.sonar.server.rule.ExternalRuleCreator;
 import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -75,14 +77,13 @@ public class RuleRepositoryImplTest {
 
   @org.junit.Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
-  @org.junit.Rule
-  public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
 
   private DbClient dbClient = mock(DbClient.class);
   private DbSession dbSession = mock(DbSession.class);
   private RuleDao ruleDao = mock(RuleDao.class);
 
-  private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE);
+  private RuleIndexer ruleIndexer = mock(RuleIndexer.class);
+  private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(db.getDbClient(), System2.INSTANCE, ruleIndexer);
   private RuleRepositoryImpl underTest = new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder);
 
   @Before
@@ -272,10 +273,6 @@ public class RuleRepositoryImplTest {
 
   @Test
   public void accept_new_externally_defined_Rules() {
-    DbClient dbClient = db.getDbClient();
-    externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE);
-    underTest = new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder);
-
     RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
 
     underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
@@ -299,10 +296,7 @@ public class RuleRepositoryImplTest {
 
   @Test
   public void persist_new_externally_defined_Rules() {
-    DbClient dbClient = db.getDbClient();
-    DbSession dbSession = dbClient.openSession(false);
-    externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE);
-    underTest = new RuleRepositoryImpl(externalRuleCreator, dbClient, analysisMetadataHolder);
+    underTest = new RuleRepositoryImpl(externalRuleCreator, db.getDbClient(), analysisMetadataHolder);
 
     RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
     underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
@@ -314,18 +308,17 @@ public class RuleRepositoryImplTest {
       .setType(BUG)
       .build());
 
-    underTest.persistNewExternalRules(dbSession);
-
-    dbSession.commit();
+    underTest.persistNewExternalRules(db.getSession());
+    db.commit();
 
-    RuleDao ruleDao = dbClient.ruleDao();
-    Optional<RuleDefinitionDto> ruleDefinitionDto = ruleDao.selectDefinitionByKey(dbClient.openSession(false), ruleKey);
+    Optional<RuleDefinitionDto> ruleDefinitionDto = db.getDbClient().ruleDao().selectDefinitionByKey(db.getSession(), ruleKey);
     assertThat(ruleDefinitionDto).isPresent();
 
     Rule rule = underTest.getByKey(ruleKey);
     assertThat(rule).isNotNull();
 
     assertThat(underTest.getById(ruleDefinitionDto.get().getId())).isNotNull();
+    verify(ruleIndexer).commitAndIndex(db.getSession(), ruleDefinitionDto.get().getId());
   }
 
   private void expectNullRuleKeyNPE() {
index d3f2a9d33dbba96110f84d62bc13daf0f4f51c2d..219470ccf180bc46108abf9112f9ac651799597c 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.computation.task.projectanalysis.step;
 
+import java.util.Optional;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,8 +37,7 @@ import org.sonar.server.computation.task.step.ComputationStep;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.rule.ExternalRuleCreator;
 import org.sonar.server.rule.index.RuleIndexDefinition;
-
-import java.util.Optional;
+import org.sonar.server.rule.index.RuleIndexer;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.api.rule.Severity.BLOCKER;
@@ -60,7 +60,8 @@ public class PersistExternalRulesStepTest extends BaseStepTest {
   @org.junit.Rule
   public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
 
-  private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE);
+  private RuleIndexer indexer = new RuleIndexer(es.client(), dbClient);
+  private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE, indexer);
 
   @Override
   protected ComputationStep step() {
@@ -74,7 +75,7 @@ public class PersistExternalRulesStepTest extends BaseStepTest {
   }
 
   @Test
-  public void persist_new_external_rules() {
+  public void persist_and_index_new_external_rules() {
 
     RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
     ruleRepository.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
@@ -102,7 +103,28 @@ public class PersistExternalRulesStepTest extends BaseStepTest {
     assertThat(reloaded.getName()).isEqualTo("disallow assignment operators in conditional statements (no-cond-assign)");
     assertThat(reloaded.getPluginKey()).isEqualTo("eslint");
 
+    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isEqualTo(1l);
+    assertThat(es.getDocuments(RuleIndexDefinition.INDEX_TYPE_RULE).iterator().next().getId()).isEqualTo(Integer.toString(reloaded.getId()));
+  }
+
+  @Test
+  public void do_not_persist_existing_external_rules() {
+    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
+    db.rules().insert(ruleKey, r -> r.setIsExternal(true));
+    ruleRepository.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
+      .setKey(ruleKey)
+      .setPluginKey("eslint")
+      .setName("disallow assignment operators in conditional statements (no-cond-assign)")
+      .setDescriptionUrl("https://eslint.org/docs/rules/no-cond-assign")
+      .setSeverity(BLOCKER)
+      .setType(BUG)
+      .build());
+
+    underTest.execute();
 
+    RuleDao ruleDao = dbClient.ruleDao();
+    assertThat(ruleDao.selectAllDefinitions(dbClient.openSession(false))).hasSize(1);
+    assertThat(es.countDocuments(RuleIndexDefinition.INDEX_TYPE_RULE)).isZero();
   }
 
 }
index e57c3dd6729560ef8182a39b19ff7dcb72b913a0..0b4eaa16e1e14cb5f77a70ada93d9e3f44a14ab2 100644 (file)
@@ -89,7 +89,7 @@ public class PersistIssuesStepTest extends BaseStepTest {
   @org.junit.Rule
   public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
 
-  private ExternalRuleCreator externalRuleCreator = new ExternalRuleCreator(dbClient, System2.INSTANCE);
+  private ExternalRuleCreator externalRuleCreator = mock(ExternalRuleCreator.class);
 
   @Override
   protected ComputationStep step() {
index 2beeddfc76c1e3799420cc837d79f665764b697c..ca23d02eb4a7c34a29fbc9de1d63f12904bd9f94 100644 (file)
 package org.sonar.server.rule;
 
 import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.server.computation.task.projectanalysis.issue.NewExternalRule;
 import org.sonar.server.computation.task.projectanalysis.issue.Rule;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.api.rule.Severity.BLOCKER;
@@ -35,13 +39,15 @@ public class ExternalRuleCreatorTest {
 
   @org.junit.Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private ExternalRuleCreator underTest = new ExternalRuleCreator(dbTester.getDbClient(), System2.INSTANCE);
+  @org.junit.Rule
+  public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
+  
+  private RuleIndexer indexer = new RuleIndexer(es.client(), dbTester.getDbClient());
+  private ExternalRuleCreator underTest = new ExternalRuleCreator(dbTester.getDbClient(), System2.INSTANCE, indexer);
   private DbSession dbSession = dbTester.getSession();
 
   @Test
   public void create_external_rule() {
-
     RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
     NewExternalRule externalRule = new NewExternalRule.Builder()
       .setKey(ruleKey)
@@ -52,7 +58,7 @@ public class ExternalRuleCreatorTest {
       .setType(BUG)
       .build();
 
-    Rule rule1 = underTest.create(dbSession, externalRule);
+    Rule rule1 = underTest.persistAndIndex(dbSession, externalRule);
 
     assertThat(rule1).isNotNull();
     assertThat(rule1.isExternal()).isTrue();
index 4c961c262dd9657b31da1290b3d35a87ad78b033..455a9a841932cd129cbe35fb64eb2a11d9ab82bb 100644 (file)
@@ -63,6 +63,7 @@ import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.rules.RuleType.VULNERABILITY;
 import static org.sonar.db.rule.RuleTesting.setCreatedAt;
 import static org.sonar.db.rule.RuleTesting.setIsTemplate;
+import static org.sonar.db.rule.RuleTesting.setIsExternal;
 import static org.sonar.db.rule.RuleTesting.setLanguage;
 import static org.sonar.db.rule.RuleTesting.setName;
 import static org.sonar.db.rule.RuleTesting.setOrganization;
@@ -366,7 +367,7 @@ public class RuleIndexTest {
 
     // find all
     RuleQuery query = new RuleQuery();
-    SearchIdResult results = underTest.search(query, new SearchOptions());
+    SearchIdResult<Integer>  results = underTest.search(query, new SearchOptions());
     assertThat(results.getIds()).hasSize(2);
 
     // Only template
@@ -384,6 +385,33 @@ public class RuleIndexTest {
     results = underTest.search(query, new SearchOptions());
     assertThat(results.getIds()).containsOnly(ruleIsTemplate.getId(), ruleNoTemplate.getId());
   }
+  
+  @Test
+  public void search_by_is_external() {
+    RuleDefinitionDto ruleIsNotExternal = createRule(setIsExternal(false));
+    RuleDefinitionDto ruleIsExternal = createRule(setIsExternal(true));
+    index();
+
+    // find all
+    RuleQuery query = new RuleQuery();
+    SearchIdResult<Integer> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getIds()).hasSize(2);
+
+    // Only template
+    query = new RuleQuery().setIsTemplate(true);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getIds()).containsOnly(ruleIsExternal.getId());
+
+    // Only not template
+    query = new RuleQuery().setIsTemplate(false);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getIds()).containsOnly(ruleIsNotExternal.getId());
+
+    // null => no filter
+    query = new RuleQuery().setIsTemplate(null);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getIds()).containsOnly(ruleIsExternal.getId(), ruleIsNotExternal.getId());
+  }
 
   @Test
   public void search_by_template_key() {
index ea3c0761a6fab7fc47180da75be883fb311af5a9..49edcf2572334ae1eade1854487fc3b392ec71b8 100644 (file)
@@ -51,13 +51,14 @@ 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.db.qualityprofile.ActiveRuleDto.INHERITED;
 import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
-import static org.sonar.server.rule.ws.SearchAction.defineRuleSearchParameters;
+import static org.sonar.server.rule.ws.SearchAction.defineGenericRuleSearchParameters;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_EXTERNAL;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
@@ -99,6 +100,7 @@ public class RuleQueryFactoryTest {
     assertThat(result.isAscendingSort()).isTrue();
     assertThat(result.getAvailableSinceLong()).isNull();
     assertThat(result.getInheritance()).isNull();
+    assertThat(result.isExternal()).isNull();
     assertThat(result.isTemplate()).isNull();
     assertThat(result.getLanguages()).isNull();
     assertThat(result.getQueryText()).isNull();
@@ -126,7 +128,8 @@ public class RuleQueryFactoryTest {
       PARAM_ACTIVE_SEVERITIES, "MINOR,MAJOR",
       PARAM_AVAILABLE_SINCE, "2016-01-01",
       PARAM_INHERITANCE, "INHERITED,OVERRIDES",
-      PARAM_IS_TEMPLATE, "true",
+      PARAM_IS_TEMPLATE, "true",      
+      PARAM_IS_EXTERNAL, "true",
       PARAM_LANGUAGES, "java,js",
       TEXT_QUERY, "S001",
       PARAM_ORGANIZATION, organization.getKey(),
@@ -149,6 +152,7 @@ public class RuleQueryFactoryTest {
     assertThat(result.isAscendingSort()).isFalse();
     assertThat(result.getAvailableSinceLong()).isNotNull();
     assertThat(result.getInheritance()).containsOnly(INHERITED, OVERRIDES);
+    assertThat(result.isExternal()).isTrue();
     assertThat(result.isTemplate()).isTrue();
     assertThat(result.getLanguages()).containsOnly(qualityProfile.getLanguage());
     assertThat(result.getQueryText()).isEqualTo("S001");
@@ -308,7 +312,7 @@ public class RuleQueryFactoryTest {
     public void define(WebService.NewController controller) {
       WebService.NewAction action = controller.createAction("fake")
         .setHandler(this);
-      defineRuleSearchParameters(action);
+      defineGenericRuleSearchParameters(action);
     }
 
     @Override
index 7bca93aab8b711fcc28a4c890d4fa300798b77aa..ee45d81f8b7b52f5e45e842c34ab5fcf5022c58c 100644 (file)
@@ -120,6 +120,7 @@ message Rule {
   optional bool remFnOverloaded = 45;
   optional string gapDescription = 44;
   optional sonarqube.ws.commons.RuleScope scope = 46;
+  optional bool isExternal = 47;
 
   optional sonarqube.ws.commons.RuleType type = 37;