]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16302 add management of indexing for rules description sections
authorLéo Geoffroy <99647462+leo-geoffroy-sonarsource@users.noreply.github.com>
Wed, 27 Apr 2022 08:53:10 +0000 (10:53 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 6 May 2022 20:02:43 +0000 (20:02 +0000)
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.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-server-common/src/main/java/org/sonar/server/rule/HotspotRuleDescription.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java

index 76dc0684d9e61ee8cf8d3ed351778cb141adcb6d..444f0e645223fb0b3fd378c276a481b6c005064a 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.rule;
 
+import java.util.Objects;
 import java.util.StringJoiner;
 
 import static org.sonar.api.utils.Preconditions.checkArgument;
@@ -73,6 +74,23 @@ public class RuleDescriptionSectionDto {
       .toString();
   }
 
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    RuleDescriptionSectionDto that = (RuleDescriptionSectionDto) o;
+    return Objects.equals(uuid, that.uuid) && Objects.equals(key, that.key) && Objects.equals(description, that.description);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(uuid, key, description);
+  }
+
   public static final class RuleDescriptionSectionDtoBuilder {
     private String uuid;
     private String key = null;
index afb8a4af448d7ca6521bbc05f7a328a174480f14..58b88632d1a94585f98db763c21617cf5bf954fc 100644 (file)
  */
 package org.sonar.db.rule;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.RuleType;
 
+import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
+
 public class RuleForIndexingDto {
 
   private String uuid;
   private String repository;
   private String pluginRuleKey;
   private String name;
-  private String description;
   private RuleDto.Format descriptionFormat;
   private Integer severity;
   private RuleStatus status;
@@ -48,6 +52,8 @@ public class RuleForIndexingDto {
   private long createdAt;
   private long updatedAt;
 
+  private Set<RuleDescriptionSectionDto> ruleDescriptionSectionsDtos = new HashSet<>();
+
   public RuleForIndexingDto() {
     // nothing to do here
   }
@@ -60,22 +66,30 @@ public class RuleForIndexingDto {
     return repository;
   }
 
+  public void setRepository(String repository) {
+    this.repository = repository;
+  }
+
   public String getPluginRuleKey() {
     return pluginRuleKey;
   }
 
-  public String getName() {
-    return name;
+  public void setPluginRuleKey(String pluginRuleKey) {
+    this.pluginRuleKey = pluginRuleKey;
   }
 
-  public String getDescription() {
-    return description;
+  public String getName() {
+    return name;
   }
 
   public RuleDto.Format getDescriptionFormat() {
     return descriptionFormat;
   }
 
+  public void setDescriptionFormat(RuleDto.Format descriptionFormat) {
+    this.descriptionFormat = descriptionFormat;
+  }
+
   public Integer getSeverity() {
     return severity;
   }
@@ -144,4 +158,21 @@ public class RuleForIndexingDto {
   public RuleKey getRuleKey() {
     return RuleKey.of(repository, pluginRuleKey);
   }
+
+  public Set<RuleDescriptionSectionDto> getRuleDescriptionSectionsDtos() {
+    return ruleDescriptionSectionsDtos;
+  }
+
+  public void setRuleDescriptionSectionsDtos(Set<RuleDescriptionSectionDto> ruleDescriptionSectionsDtos) {
+    this.ruleDescriptionSectionsDtos = ruleDescriptionSectionsDtos;
+  }
+
+  private Optional<RuleDescriptionSectionDto> findExistingSectionWithSameKey(String ruleDescriptionSectionKey) {
+    return ruleDescriptionSectionsDtos.stream().filter(section -> section.getKey().equals(ruleDescriptionSectionKey)).findAny();
+  }
+
+  @CheckForNull
+  public RuleDescriptionSectionDto getDefaultRuleDescriptionSectionDto() {
+    return findExistingSectionWithSameKey(DEFAULT_KEY).orElse(null);
+  }
 }
index 0188bf511d8c5d333ae57174f183f7170dd69585..bef6fde79b5eed27efe9ac17fa514c1ade513315 100644 (file)
   <resultMap id="ruleResultMap" type="org.sonar.db.rule.RuleDto">
     <id property="uuid" column="r_uuid"/>
 
-    <result property="createdAtFromDefinition" column="createdAtFromDefinition" />
-    <result property="updatedAtFromDefinition" column="updatedAtFromDefinition" />
-    <result property="noteData" column="noteData" />
-    <result property="noteUserUuid" column="noteUserUuid" />
-    <result property="noteCreatedAt" column="noteCreatedAt" />
-    <result property="noteUpdatedAt" column="noteUpdatedAt" />
-    <result property="remediationFunction" column="remediationFunction" />
-    <result property="remediationGapMultiplier" column="remediationGapMultiplier" />
-    <result property="remediationBaseEffort" column="remediationBaseEffort" />
-    <result property="tagsField" column="tagsField" />
-    <result property="adHocName" column="adHocName" />
-    <result property="adHocDescription" column="adHocDescription" />
-    <result property="adHocSeverity" column="adHocSeverity" />
-    <result property="adHocType" column="adHocType" />
-    <result property="createdAtFromMetadata" column="createdAtFromMetadata" />
-    <result property="updatedAtFromMetadata" column="updatedAtFromMetadata"  />
+    <result property="createdAtFromDefinition" column="createdAtFromDefinition"/>
+    <result property="updatedAtFromDefinition" column="updatedAtFromDefinition"/>
+    <result property="noteData" column="noteData"/>
+    <result property="noteUserUuid" column="noteUserUuid"/>
+    <result property="noteCreatedAt" column="noteCreatedAt"/>
+    <result property="noteUpdatedAt" column="noteUpdatedAt"/>
+    <result property="remediationFunction" column="remediationFunction"/>
+    <result property="remediationGapMultiplier" column="remediationGapMultiplier"/>
+    <result property="remediationBaseEffort" column="remediationBaseEffort"/>
+    <result property="tagsField" column="tagsField"/>
+    <result property="adHocName" column="adHocName"/>
+    <result property="adHocDescription" column="adHocDescription"/>
+    <result property="adHocSeverity" column="adHocSeverity"/>
+    <result property="adHocType" column="adHocType"/>
+    <result property="createdAtFromMetadata" column="createdAtFromMetadata"/>
+    <result property="updatedAtFromMetadata" column="updatedAtFromMetadata"/>
 
     <result property="ruleKey" column="ruleKey"/>
     <result property="repositoryKey" column="repositoryKey"/>
 
   </resultMap>
 
+  <resultMap id="ruleForIndexingDtoResultMap" type="org.sonar.db.rule.RuleForIndexingDto">
+    <result property="uuid" column="uuid"/>
+    <result property="repository" column="repository"/>
+    <result property="pluginRuleKey" column="pluginRuleKey"/>
+    <result property="name" column="name"/>
+    <result property="descriptionFormat" column="descriptionFormat"/>
+    <result property="severity" column="severity"/>
+    <result property="status" column="status"/>
+    <result property="isTemplate" column="isTemplate"/>
+    <result property="isExternal" column="isExternal"/>
+    <result property="systemTags" column="systemTags"/>
+    <result property="securityStandards" column="securityStandards"/>
+    <result property="templateRuleKey" column="templateRuleKey"/>
+    <result property="templateRepository" column="templateRepository"/>
+    <result property="internalKey" column="internalKey"/>
+    <result property="language" column="language"/>
+    <result property="type" column="type"/>
+    <result property="createdAt" column="createdAt"/>
+    <result property="updatedAt" column="updatedAt"/>
+    <result property="tags" column="tags"/>
+    <collection property="ruleDescriptionSectionsDtos" ofType="org.sonar.db.rule.RuleDescriptionSectionDto">
+      <id property="uuid" column="rds_uuid"/>
+      <result property="key" column="rds_kee"/>
+      <result property="description" column="rds_description"/>
+    </collection>
+  </resultMap>
+
   <select id="selectAllDefinitions" resultMap="ruleDefinitionResultMap">
     select
       <include refid="selectRuleTableColumns"/>
       </foreach>
   </select>
 
-  <select id="selectIndexingRulesByUuids" parameterType="map" resultType="org.sonar.db.rule.RuleForIndexingDto">
+  <select id="selectIndexingRulesByUuids" parameterType="map" resultMap="ruleForIndexingDtoResultMap">
     <include refid="sqlSelectIndexingRules"/>
     where
       <foreach collection="ruleUuids" index="index" item="ruleUuid" open="" separator=" or " close="">
     order by r.created_at asc
   </select>
 
-  <select id="scrollIndexingRules" resultType="org.sonar.db.rule.RuleForIndexingDto" fetchSize="${_scrollFetchSize}"
-          resultSetType="FORWARD_ONLY">
+  <select id="scrollIndexingRules" resultMap="ruleForIndexingDtoResultMap" fetchSize="${_scrollFetchSize}"
+          resultSetType="FORWARD_ONLY" resultOrdered="true">
     <include refid="sqlSelectIndexingRules"/>
-    order by r.created_at asc
+    order by r.created_at,r.uuid asc
   </select>
 
   <sql id="sqlSelectIndexingRules">
       r.rule_type as "type",
       r.created_at as "createdAt",
       r.updated_at as "updatedAt",
-      rm.tags as "tags"
+      rm.tags as "tags",
+      rds.uuid as "rds_uuid",
+      rds.kee as "rds_kee",
+      rds.description as "rds_description"
     from rules r
     left outer join rules t on t.uuid = r.template_uuid
     left outer join rules_metadata rm on r.uuid = rm.rule_uuid
+    left outer join rule_desc_sections rds on
+      rds.rule_uuid = r.uuid
   </sql>
 
   <select id="selectByQuery" parameterType="map" resultMap="ruleResultMap">
index c68c59ad0bfc77e20c45a5ade53c09cdfbf8eb7b..d1b7857156fd2622f5013745ccedde15cd429510 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.rule;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.MoreCollectors;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -833,7 +834,14 @@ public class RuleDaoTest {
   @Test
   public void scrollIndexingRules() {
     Accumulator<RuleForIndexingDto> accumulator = new Accumulator<>();
-    RuleDefinitionDto r1 = db.rules().insert();
+    RuleDescriptionSectionDto ruleDescriptionSectionDto = RuleDescriptionSectionDto.builder()
+      .key("DESC")
+      .uuid("uuid")
+      .description("my description")
+      .build();
+    RuleDefinitionDto r1 = db.rules().insert(r -> {
+      r.addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
+    });
     RuleDefinitionDto r2 = db.rules().insert(r -> r.setIsExternal(true));
 
     underTest.scrollIndexingRules(db.getSession(), accumulator);
@@ -847,8 +855,10 @@ public class RuleDaoTest {
     assertThat(firstRule.getRepository()).isEqualTo(r1.getRepositoryKey());
     assertThat(firstRule.getPluginRuleKey()).isEqualTo(r1.getRuleKey());
     assertThat(firstRule.getName()).isEqualTo(r1.getName());
-    //FIXME SONAR-16309
-    //assertThat(firstRule.getDescription()).isEqualTo(r1.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
+    assertThat(firstRule.getRuleDescriptionSectionsDtos().stream()
+      .filter(s -> s.getKey().equals(ruleDescriptionSectionDto.getKey()))
+      .collect(MoreCollectors.onlyElement()))
+      .isEqualTo(ruleDescriptionSectionDto);
     assertThat(firstRule.getDescriptionFormat()).isEqualTo(r1.getDescriptionFormat());
     assertThat(firstRule.getSeverity()).isEqualTo(r1.getSeverity());
     assertThat(firstRule.getStatus()).isEqualTo(r1.getStatus());
@@ -927,8 +937,7 @@ public class RuleDaoTest {
     assertThat(firstRule.getRepository()).isEqualTo(r1.getRepositoryKey());
     assertThat(firstRule.getPluginRuleKey()).isEqualTo(r1.getRuleKey());
     assertThat(firstRule.getName()).isEqualTo(r1.getName());
-    //FIXME SONAR-16309
-    //assertThat(firstRule.getDescription()).isEqualTo(r1.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
+    assertThat(firstRule.getRuleDescriptionSectionsDtos()).isEqualTo(r1.getRuleDescriptionSectionDtos());
     assertThat(firstRule.getDescriptionFormat()).isEqualTo(r1.getDescriptionFormat());
     assertThat(firstRule.getSeverity()).isEqualTo(r1.getSeverity());
     assertThat(firstRule.getSeverityAsString()).isEqualTo(SeverityUtil.getSeverityFromOrdinal(r1.getSeverity()));
index b3b27628cb009bd31cf61c1ae753245444fbb49f..c59cb3cb362df8e70c10368f2f4be3517f353587 100644 (file)
@@ -53,7 +53,7 @@ public class HotspotRuleDescription {
   }
 
   public static HotspotRuleDescription from(RuleForIndexingDto dto) {
-    return from(dto.getDescription());
+    return from(RuleDescriptionFormatter.getDescriptionAsHtml(dto));
   }
 
   private static HotspotRuleDescription from(@Nullable String description) {
index 2cae26e26958a82a1a18486c1bdfbfc82095e0d7..f4a61eec7cdd47786581e6d2526816652ddd1da0 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Optional;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.markdown.Markdown;
 
 import static com.google.common.collect.MoreCollectors.toOptional;
@@ -39,28 +40,42 @@ public class RuleDescriptionFormatter {
       return null;
     }
     Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDefinitionDto.getRuleDescriptionSectionDtos();
+    return retrieveDescription(ruleDescriptionSectionDtos, ruleDefinitionDto.getRuleKey(), ruleDefinitionDto.getDescriptionFormat());
+  }
+
+  public static String getDescriptionAsHtml(RuleForIndexingDto ruleForIndexingDto) {
+    if (ruleForIndexingDto.getDescriptionFormat() == null) {
+      return null;
+    }
+    Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleForIndexingDto.getRuleDescriptionSectionsDtos();
+    return retrieveDescription(ruleDescriptionSectionDtos, ruleForIndexingDto.getRuleKey().toString(), ruleForIndexingDto.getDescriptionFormat());
+  }
+
+  private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos,
+    String ruleKey, RuleDto.Format descriptionFormat) {
     Optional<RuleDescriptionSectionDto> ruleDescriptionSectionDto = findDefaultDescription(ruleDescriptionSectionDtos);
     return ruleDescriptionSectionDto
-      .map(ruleDescriptionSection -> toHtml(ruleDefinitionDto, ruleDescriptionSection))
+      .map(ruleDescriptionSection -> toHtml(ruleKey, descriptionFormat, ruleDescriptionSection))
       .orElse(null);
   }
 
+
   private static Optional<RuleDescriptionSectionDto> findDefaultDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos) {
     return ruleDescriptionSectionDtos.stream()
       .filter(RuleDescriptionSectionDto::isDefault)
       .collect(toOptional());
   }
 
-  private static String toHtml(RuleDefinitionDto ruleDefinitionDto, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
-    RuleDto.Format descriptionFormat = Objects.requireNonNull(ruleDefinitionDto.getDescriptionFormat(),
-      "Rule " + ruleDefinitionDto.getDescriptionFormat() + " contains section(s) but has no format set");
-    switch (descriptionFormat) {
+  private static String toHtml(String ruleKey, RuleDto.Format descriptionFormat, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    RuleDto.Format nonNullDescriptionFormat = Objects.requireNonNull(descriptionFormat,
+      "Rule " + descriptionFormat + " contains section(s) but has no format set");
+    switch (nonNullDescriptionFormat) {
       case MARKDOWN:
         return Markdown.convertToHtml(ruleDescriptionSectionDto.getDescription());
       case HTML:
         return ruleDescriptionSectionDto.getDescription();
       default:
-        throw new IllegalStateException(format("Rule description section format '%s' is unknown for rule key '%s'", descriptionFormat, ruleDefinitionDto.getKey()));
+        throw new IllegalStateException(format("Rule description section format '%s' is unknown for rule key '%s'", descriptionFormat, ruleKey));
     }
   }
 
index 5ce56bdec82d48fa83b35c3eec5e67792a5ebe04..292dbfb30a8cbb0c85c38932859ef7c2b85c6ecf 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleForIndexingDto;
 import org.sonar.markdown.Markdown;
 import org.sonar.server.es.BaseDoc;
+import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.security.SecurityStandards;
 import org.sonar.server.security.SecurityStandards.SQCategory;
 
@@ -318,13 +319,8 @@ public class RuleDoc extends BaseDoc {
       ruleDoc.setTemplateKey(null);
     }
 
-    if (dto.getDescription() != null && dto.getDescriptionFormat() != null) {
-      if (RuleDto.Format.HTML == dto.getDescriptionFormat()) {
-        ruleDoc.setHtmlDescription(dto.getDescription());
-      } else {
-        ruleDoc.setHtmlDescription(Markdown.convertToHtml(dto.getDescription()));
-      }
-    }
+    String descriptionAsHtml = RuleDescriptionFormatter.getDescriptionAsHtml(dto);
+    ruleDoc.setHtmlDescription(descriptionAsHtml);
     return ruleDoc;
   }
 }
index ca8ca7d6da292697ae4a4a58d4a65343f7c11424..25e492ebb80cdc92f52aef16e7bf995f7b253f2a 100644 (file)
  */
 package org.sonar.server.rule;
 
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleForIndexingDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
@@ -60,4 +65,39 @@ public class RuleDescriptionFormatterTest {
     String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
     assertThat(result).isNull();
   }
+
+  @Test
+  public void getHtmlDescriptionForRuleForIndexingDtoAsIs() {
+    Set<RuleDescriptionSectionDto> sectionsDtos = Sets.newHashSet(
+      createDefaultRuleDescriptionSection("uuid", HTML_SECTION.getDescription()));
+    RuleForIndexingDto rule = createRuleForIndexingDto(sectionsDtos, RuleDto.Format.HTML);
+    String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
+    assertThat(html).isEqualTo(HTML_SECTION.getDescription());
+  }
+
+  @Test
+  public void handleEmptyDescriptionForRuleForIndexingDto() {
+    RuleForIndexingDto rule = createRuleForIndexingDto(Collections.emptySet(), RuleDto.Format.HTML);
+    String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
+    assertThat(result).isNull();
+  }
+
+  @Test
+  public void handleNullDescriptionFormatForRuleForIndexingDto() {
+    Set<RuleDescriptionSectionDto> sectionsDtos = Sets.newHashSet(
+      createDefaultRuleDescriptionSection("uuid", HTML_SECTION.getDescription()));
+    RuleForIndexingDto rule = createRuleForIndexingDto(sectionsDtos, null);
+    String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
+    assertThat(result).isNull();
+  }
+
+  @NotNull
+  private static RuleForIndexingDto createRuleForIndexingDto(Set<RuleDescriptionSectionDto> sectionsDtos, RuleDto.Format format) {
+    RuleForIndexingDto rule = new RuleForIndexingDto();
+    rule.setRuleDescriptionSectionsDtos(sectionsDtos);
+    rule.setDescriptionFormat(format);
+    rule.setRepository("repository");
+    rule.setPluginRuleKey("pluginKey");
+    return rule;
+  }
 }