diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2022-06-24 10:51:35 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-06-30 20:03:09 +0000 |
commit | 0eddb29f5b003ee4600bf230c5533215cf7515ff (patch) | |
tree | 37fe3bf9f6964dc326145ceb2a6d0fcde373ee18 /server/sonar-webserver-core | |
parent | 923ec14b8ef55af36ff9bc45988b5c8fd51043bd (diff) | |
download | sonarqube-0eddb29f5b003ee4600bf230c5533215cf7515ff.tar.gz sonarqube-0eddb29f5b003ee4600bf230c5533215cf7515ff.zip |
SONAR-16518 Read contextual descriptions from sonar-plugin-api and pass them to RuleDescriptionSectionDto
Diffstat (limited to 'server/sonar-webserver-core')
3 files changed, 181 insertions, 9 deletions
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java index 1fb1afaf8e2..08652f93ee4 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java @@ -19,9 +19,14 @@ */ package org.sonar.server.rule; +import java.util.Optional; import java.util.Set; +import org.jetbrains.annotations.Nullable; +import org.sonar.api.server.rule.Context; +import org.sonar.api.server.rule.RuleDescriptionSection; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.core.util.UuidFactory; +import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import static java.util.stream.Collectors.toSet; @@ -41,13 +46,24 @@ public class AdvancedRuleDescriptionSectionsGenerator implements RuleDescription @Override public Set<RuleDescriptionSectionDto> generateSections(RulesDefinition.Rule rule) { return rule.ruleDescriptionSections().stream() - .map(section -> RuleDescriptionSectionDto.builder() - .uuid(uuidFactory.create()) - .key(section.getKey()) - .content(section.getHtmlContent()) - .build() - ) + .map(this::toRuleDescriptionSectionDto) .collect(toSet()); } + private RuleDescriptionSectionDto toRuleDescriptionSectionDto(RuleDescriptionSection section) { + return RuleDescriptionSectionDto.builder() + .uuid(uuidFactory.create()) + .key(section.getKey()) + .content(section.getHtmlContent()) + .context(toRuleDescriptionSectionContextDto(section.getContext())) + .build(); + } + + @Nullable + private static RuleDescriptionSectionContextDto toRuleDescriptionSectionContextDto(Optional<Context> context) { + return context + .map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())) + .orElse(null); + } + } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java index e7f6a0229d9..08c4a4f4df8 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java @@ -27,16 +27,19 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.api.server.rule.Context; import org.sonar.api.server.rule.RuleDescriptionSection; import org.sonar.api.server.rule.RuleDescriptionSectionBuilder; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.core.util.UuidFactory; +import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; +import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; @RunWith(MockitoJUnitRunner.class) @@ -49,10 +52,39 @@ public class AdvancedRuleDescriptionSectionsGeneratorTest { private static final RuleDescriptionSection SECTION_1 = new RuleDescriptionSectionBuilder().sectionKey(HOW_TO_FIX_SECTION_KEY).htmlContent(HTML_CONTENT).build(); private static final RuleDescriptionSection SECTION_2 = new RuleDescriptionSectionBuilder().sectionKey(ROOT_CAUSE_SECTION_KEY).htmlContent(HTML_CONTENT + "2").build(); + private static final Context CONTEXT_1 = new Context("CTX_1", "ctx 1 display name"); + private static final RuleDescriptionSection SECTION_3_WITH_CTX_1 = new RuleDescriptionSectionBuilder() + .sectionKey(RESOURCES_SECTION_KEY) + .htmlContent(HTML_CONTENT) + .context(CONTEXT_1) + .build(); + + private static final Context CONTEXT_2 = new Context("CTX_2", "ctx 2 display name"); + private static final RuleDescriptionSection SECTION_3_WITH_CTX_2 = new RuleDescriptionSectionBuilder() + .sectionKey(RESOURCES_SECTION_KEY) + .htmlContent(HTML_CONTENT + "2") + .context(CONTEXT_2) + .build(); + private static final RuleDescriptionSectionDto EXPECTED_SECTION_1 = RuleDescriptionSectionDto.builder().uuid(UUID_1).key(HOW_TO_FIX_SECTION_KEY).content(HTML_CONTENT).build(); private static final RuleDescriptionSectionDto EXPECTED_SECTION_2 = RuleDescriptionSectionDto.builder().uuid(UUID_2).key(ROOT_CAUSE_SECTION_KEY) .content(HTML_CONTENT + "2").build(); + private static final RuleDescriptionSectionDto EXPECTED_SECTION_3_WITH_CTX_1 = RuleDescriptionSectionDto.builder() + .uuid(UUID_1) + .key(SECTION_3_WITH_CTX_1.getKey()) + .content(SECTION_3_WITH_CTX_1.getHtmlContent()) + .context(RuleDescriptionSectionContextDto.of(CONTEXT_1.getKey(), CONTEXT_1.getDisplayName())) + .build(); + + private static final RuleDescriptionSectionDto EXPECTED_SECTION_3_WITH_CTX_2 = RuleDescriptionSectionDto.builder() + .uuid(UUID_2) + .key(SECTION_3_WITH_CTX_2.getKey()) + .content(SECTION_3_WITH_CTX_2.getHtmlContent()) + .context(RuleDescriptionSectionContextDto.of(CONTEXT_2.getKey(), CONTEXT_2.getDisplayName())) + .build(); + + @Mock private UuidFactory uuidFactory; @@ -90,6 +122,28 @@ public class AdvancedRuleDescriptionSectionsGeneratorTest { } @Test + public void generateSections_whenTwoContextSpecificSections_createsTwoSections() { + when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_3_WITH_CTX_1, SECTION_3_WITH_CTX_2)); + + Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); + + assertThat(ruleDescriptionSectionDtos) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(EXPECTED_SECTION_3_WITH_CTX_1, EXPECTED_SECTION_3_WITH_CTX_2); + } + + @Test + public void generateSections_whenContextSpecificSectionsAndNonContextSpecificSection_createsTwoSections() { + when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1, SECTION_3_WITH_CTX_2)); + + Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); + + assertThat(ruleDescriptionSectionDtos) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_3_WITH_CTX_2); + } + + @Test public void generateSections_whenNoSections_returnsEmptySet() { when(rule.ruleDescriptionSections()).thenReturn(emptyList()); diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java index 4f7c1add4f9..8dc72061466 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java @@ -25,9 +25,14 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.elasticsearch.common.util.set.Sets; +import org.jetbrains.annotations.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,6 +44,8 @@ import org.sonar.api.rule.RuleScope; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.server.rule.Context; +import org.sonar.api.server.rule.RuleDescriptionSection; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.log.LogTester; @@ -52,6 +59,7 @@ import org.sonar.db.qualityprofile.QProfileChangeDto; import org.sonar.db.qualityprofile.QProfileChangeQuery; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.rule.DeprecatedRuleKeyDto; +import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleDto.Scope; @@ -71,6 +79,7 @@ import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.rule.index.RuleQuery; import static com.google.common.collect.Sets.newHashSet; +import static java.lang.String.format; import static java.lang.String.valueOf; import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; @@ -89,7 +98,10 @@ import static org.sonar.api.rule.RuleStatus.READY; import static org.sonar.api.rule.RuleStatus.REMOVED; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.api.rule.Severity.INFO; -import static org.sonar.api.server.rule.RulesDefinition.Context; +import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; +import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; +import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; +import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; import static org.sonar.api.server.rule.RulesDefinition.NewRepository; import static org.sonar.api.server.rule.RulesDefinition.NewRule; import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10; @@ -145,7 +157,17 @@ public class RegisterRulesTest { when(ruleDescriptionSectionsGenerator.generateSections(any())).thenAnswer(answer -> { RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class); String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription(); - return Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()); + + Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream() // + .map(s -> builder() + .uuid(UuidFactoryFast.getInstance().create()) + .key(s.getKey()) + .content(s.getHtmlContent()) + .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null)) + .build() + ) + .collect(Collectors.toSet()); + return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build())); }); when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true); @@ -718,6 +740,86 @@ public class RegisterRulesTest { } @Test + public void update_several_rule_descriptions() { + system.setNow(DATE1.getTime()); + + RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "CTX_1"); + RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY,"section1 ctx2 content", "CTX_2"); + RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 content", "CTX_1"); + RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY,"section3 content", null); + RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY,"section4 content", null); + execute(context -> { + NewRepository repo = context.createRepository("fake", "java"); + repo.createRule("rule") + .setName("Name") + .addDescriptionSection(section1context1) + .addDescriptionSection(section1context2) + .addDescriptionSection(section2context1) + .addDescriptionSection(section3noContext) + .addDescriptionSection(section4noContext) + .setHtmlDescription("Desc1"); + repo.done(); + }); + + RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "CTX_2"); + RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null); + RuleDescriptionSection section4updatedWithContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "CTX_1"); + system.setNow(DATE2.getTime()); + execute(context -> { + NewRepository repo = context.createRepository("fake", "java"); + repo.createRule("rule") + .setName("Name") + .addDescriptionSection(section1context1) + .addDescriptionSection(section1context2updated) + .addDescriptionSection(section2updatedWithoutContext) + .addDescriptionSection(section3noContext) + .addDescriptionSection(section4updatedWithContext) + .setHtmlDescription("Desc2"); + repo.done(); + + }); + + RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule")); + assertThat(rule1.getName()).isEqualTo("Name"); + assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2"); + + Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated, + section2updatedWithoutContext, section3noContext, section4updatedWithContext); + assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1); + expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos())); + } + + private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) { + Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null); + return RuleDescriptionSection.builder().sectionKey(sectionKey) + .htmlContent(description) + .context(context) + .build(); + } + + private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) { + sectionDtos.stream() + .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent())) + .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext())) + .findAny() + .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey()))); + } + + private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) { + if (apiContext.isEmpty() && contextDto == null) { + return true; + } + return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent(); + } + + private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) { + if (contextDto == null) { + return false; + } + return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName()); + } + + @Test public void rule_previously_created_as_adhoc_becomes_none_adhoc() { RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true)); system.setNow(DATE2.getTime()); @@ -1055,7 +1157,7 @@ public class RegisterRulesTest { } @SafeVarargs - private void createRule(Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) { + private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) { NewRepository repo = context.createRepository(repositoryKey, language); NewRule newRule = repo.createRule(ruleKey) .setName(ruleKey) |