From 80d992e1d0873227d310625df8ab7a686724888a Mon Sep 17 00:00:00 2001 From: Adrien Lecharpentier Date: Wed, 21 Feb 2024 18:19:58 +0100 Subject: [PATCH] Using release drafter is not mandatory but a good documentation practice (#468) --- ...aseProbe.java => ReleaseDrafterProbe.java} | 8 +- .../scoring/scores/DocumentationScoring.java | 90 +++++++++++++++---- ...Test.java => ReleaseDrafterProbeTest.java} | 26 +++--- .../scores/DocumentationScoringTest.java | 61 ++++++++++++- 4 files changed, 147 insertions(+), 38 deletions(-) rename core/src/main/java/io/jenkins/pluginhealth/scoring/probes/{DrafterReleaseProbe.java => ReleaseDrafterProbe.java} (96%) rename core/src/test/java/io/jenkins/pluginhealth/scoring/probes/{DrafterReleaseProbeTest.java => ReleaseDrafterProbeTest.java} (87%) diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbe.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbe.java similarity index 96% rename from core/src/main/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbe.java rename to core/src/main/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbe.java index e90341534..e46e799fd 100644 --- a/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbe.java +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbe.java @@ -38,13 +38,11 @@ import org.springframework.stereotype.Component; @Component -@Order(DrafterReleaseProbe.ORDER) -public class DrafterReleaseProbe extends Probe { - +@Order(ReleaseDrafterProbe.ORDER) +public class ReleaseDrafterProbe extends Probe { public static final String KEY = "release-drafter"; - public static final int ORDER = LastCommitDateProbe.ORDER + 100; - private static final Logger LOGGER = LoggerFactory.getLogger(DrafterReleaseProbe.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ReleaseDrafterProbe.class); @Override protected ProbeResult doApply(Plugin plugin, ProbeContext context) { diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoring.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoring.java index 0218c5ac1..5f0b11e0a 100644 --- a/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoring.java +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoring.java @@ -31,8 +31,10 @@ import io.jenkins.pluginhealth.scoring.model.ProbeResult; import io.jenkins.pluginhealth.scoring.model.Resolution; import io.jenkins.pluginhealth.scoring.model.ScoringComponentResult; +import io.jenkins.pluginhealth.scoring.probes.ContinuousDeliveryProbe; import io.jenkins.pluginhealth.scoring.probes.ContributingGuidelinesProbe; import io.jenkins.pluginhealth.scoring.probes.DocumentationMigrationProbe; +import io.jenkins.pluginhealth.scoring.probes.ReleaseDrafterProbe; import org.springframework.stereotype.Component; @@ -57,26 +59,35 @@ public String description() { @Override public List getComponents() { - return List.of(new ScoringComponent() { - @Override - public String getDescription() { - return "The plugin has a specific contributing guide."; - } + return List.of( + new ScoringComponent() { + @Override + public String getDescription() { + return "The plugin has a specific contributing guide."; + } - @Override - public ScoringComponentResult getScore(Plugin plugin, Map probeResults) { - ProbeResult probeResult = probeResults.get(ContributingGuidelinesProbe.KEY); - if (probeResult != null && "Contributing guidelines found.".equals(probeResult.message())) { - return new ScoringComponentResult(100, getWeight(), List.of("Plugin has a contributing guide.")); + @Override + public ScoringComponentResult getScore(Plugin plugin, Map probeResults) { + ProbeResult probeResult = probeResults.get(ContributingGuidelinesProbe.KEY); + if (probeResult != null && "Contributing guidelines found.".equals(probeResult.message())) { + return new ScoringComponentResult(100, getWeight(), List.of("Plugin has a contributing guide.")); + } + return new ScoringComponentResult( + 0, + getWeight(), + List.of("The plugin relies on the global contributing guide."), + List.of(new Resolution( + "See why and how to add a contributing guide", + "https://www.jenkins.io/doc/developer/tutorial-improve/add-a-contributing-guide/" + )) + ); } - return new ScoringComponentResult(0, getWeight(), List.of("The plugin relies on the global contributing guide.")); - } - @Override - public int getWeight() { - return 2; - } - }, + @Override + public int getWeight() { + return 2; + } + }, new ScoringComponent() { @Override public String getDescription() { @@ -110,11 +121,52 @@ public ScoringComponentResult getScore(Plugin $, Map probeR public int getWeight() { return 8; } - }); + }, + new ScoringComponent() { + @Override + public String getDescription() { + return "Recommend to setup Release Drafter on the plugin repository."; + } + + @Override + public ScoringComponentResult getScore(Plugin plugin, Map probeResults) { + ProbeResult cdProbe = probeResults.get(ContinuousDeliveryProbe.KEY); + if (cdProbe != null && "JEP-229 workflow definition found.".equals(cdProbe.message())) { + return new ScoringComponentResult( + 100, + getWeight(), + List.of("Plugin using Release Drafter because it has CD configured.") + ); + } + ProbeResult result = probeResults.get(ReleaseDrafterProbe.KEY); + if (result != null && "Release Drafter is configured.".equals(result.message())) { + return new ScoringComponentResult( + 100, + getWeight(), + List.of("Plugin is using Release Drafter.") + ); + } + return new ScoringComponentResult( + 0, + 0, + List.of("Plugin is not using Release Drafter to manage its changelog."), + List.of(new Resolution( + "Plugin could benefit from using Release Drafter.", + "https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc" + )) + ); + } + + @Override + public int getWeight() { + return 0; + } + } + ); } @Override public int version() { - return 1; + return 2; } } diff --git a/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbeTest.java b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbeTest.java similarity index 87% rename from core/src/test/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbeTest.java rename to core/src/test/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbeTest.java index 6c17c7513..fc628e438 100644 --- a/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/DrafterReleaseProbeTest.java +++ b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/ReleaseDrafterProbeTest.java @@ -40,10 +40,10 @@ import org.junit.jupiter.api.Test; -class DrafterReleaseProbeTest extends AbstractProbeTest { +class ReleaseDrafterProbeTest extends AbstractProbeTest { @Override - DrafterReleaseProbe getSpy() { - return spy(DrafterReleaseProbe.class); + ReleaseDrafterProbe getSpy() { + return spy(ReleaseDrafterProbe.class); } @Test @@ -59,19 +59,19 @@ void shouldRequireValidSCM() { when(plugin.getName()).thenReturn("foo"); when(ctx.getScmRepository()).thenReturn(Optional.empty()); - final DrafterReleaseProbe probe = getSpy(); + final ReleaseDrafterProbe probe = getSpy(); assertThat(probe.apply(plugin, ctx)) .usingRecursiveComparison() .comparingOnlyFields("id", "message", "status") - .isEqualTo(ProbeResult.error(DrafterReleaseProbe.KEY, "There is no local repository for plugin " + plugin.getName() + ".", probe.getVersion())); + .isEqualTo(ProbeResult.error(ReleaseDrafterProbe.KEY, "There is no local repository for plugin " + plugin.getName() + ".", probe.getVersion())); } @Test void shouldDetectMissingGithubConfigurationFolder() throws Exception { final Plugin plugin = mock(Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); - final DrafterReleaseProbe probe = getSpy(); + final ReleaseDrafterProbe probe = getSpy(); final Path repo = Files.createTempDirectory("foo"); when(ctx.getScmRepository()).thenReturn(Optional.of(repo)); @@ -79,7 +79,7 @@ void shouldDetectMissingGithubConfigurationFolder() throws Exception { assertThat(probe.apply(plugin, ctx)) .usingRecursiveComparison() .comparingOnlyFields("id", "message", "status") - .isEqualTo(ProbeResult.success(DrafterReleaseProbe.KEY, "No GitHub configuration folder found.", probe.getVersion())); + .isEqualTo(ProbeResult.success(ReleaseDrafterProbe.KEY, "No GitHub configuration folder found.", probe.getVersion())); verify(probe).doApply(any(Plugin.class), any(ProbeContext.class)); } @@ -87,7 +87,7 @@ void shouldDetectMissingGithubConfigurationFolder() throws Exception { void shouldDetectMissingReleaseDrafterFile() throws Exception { final Plugin plugin = mock(Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); - final DrafterReleaseProbe probe = getSpy(); + final ReleaseDrafterProbe probe = getSpy(); final Path repo = Files.createTempDirectory("foo"); Files.createDirectories(repo.resolve(".github")); @@ -96,7 +96,7 @@ void shouldDetectMissingReleaseDrafterFile() throws Exception { assertThat(probe.apply(plugin, ctx)) .usingRecursiveComparison() .comparingOnlyFields("id", "message", "status") - .isEqualTo(ProbeResult.success(DrafterReleaseProbe.KEY, "Release Drafter is not configured.", probe.getVersion())); + .isEqualTo(ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter is not configured.", probe.getVersion())); verify(probe).doApply(any(Plugin.class), any(ProbeContext.class)); } @@ -104,7 +104,7 @@ void shouldDetectMissingReleaseDrafterFile() throws Exception { void shouldDetectReleaseDrafterFile() throws Exception { final Plugin plugin = mock(Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); - final DrafterReleaseProbe probe = getSpy(); + final ReleaseDrafterProbe probe = getSpy(); final Path repo = Files.createTempDirectory("foo"); final Path github = Files.createDirectories(repo.resolve(".github")); @@ -115,7 +115,7 @@ void shouldDetectReleaseDrafterFile() throws Exception { assertThat(probe.apply(plugin, ctx)) .usingRecursiveComparison() .comparingOnlyFields("id", "message", "status") - .isEqualTo(ProbeResult.success(DrafterReleaseProbe.KEY, "Release Drafter is configured.", probe.getVersion())); + .isEqualTo(ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter is configured.", probe.getVersion())); verify(probe).doApply(any(Plugin.class), any(ProbeContext.class)); } @@ -123,7 +123,7 @@ void shouldDetectReleaseDrafterFile() throws Exception { void shouldDetectReleaseDrafterFileWithFullFileExtension() throws Exception { final Plugin plugin = mock(Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); - final DrafterReleaseProbe probe = getSpy(); + final ReleaseDrafterProbe probe = getSpy(); final Path repo = Files.createTempDirectory("foo"); final Path github = Files.createDirectories(repo.resolve(".github")); @@ -134,7 +134,7 @@ void shouldDetectReleaseDrafterFileWithFullFileExtension() throws Exception { assertThat(probe.apply(plugin, ctx)) .usingRecursiveComparison() .comparingOnlyFields("id", "message", "status") - .isEqualTo(ProbeResult.success(DrafterReleaseProbe.KEY, "Release Drafter is configured.", probe.getVersion())); + .isEqualTo(ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter is configured.", probe.getVersion())); verify(probe).doApply(any(Plugin.class), any(ProbeContext.class)); } } diff --git a/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoringTest.java b/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoringTest.java index b39c07e97..ff2556c80 100644 --- a/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoringTest.java +++ b/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/DocumentationScoringTest.java @@ -35,9 +35,12 @@ import io.jenkins.pluginhealth.scoring.model.Plugin; import io.jenkins.pluginhealth.scoring.model.ProbeResult; import io.jenkins.pluginhealth.scoring.model.ScoreResult; +import io.jenkins.pluginhealth.scoring.probes.ContinuousDeliveryProbe; import io.jenkins.pluginhealth.scoring.probes.ContributingGuidelinesProbe; import io.jenkins.pluginhealth.scoring.probes.DocumentationMigrationProbe; +import io.jenkins.pluginhealth.scoring.probes.ReleaseDrafterProbe; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; class DocumentationScoringTest extends AbstractScoringTest { @@ -53,7 +56,8 @@ void shouldScoreOneHundredWithMigratedDocumentationAndContributingGuide() { when(plugin.getDetails()).thenReturn(Map.of( DocumentationMigrationProbe.KEY, ProbeResult.success(DocumentationMigrationProbe.KEY, "Documentation is located in the plugin repository.", 1), - ContributingGuidelinesProbe.KEY, ProbeResult.success(ContributingGuidelinesProbe.KEY, "Contributing guidelines found.", 1) + ContributingGuidelinesProbe.KEY, ProbeResult.success(ContributingGuidelinesProbe.KEY, "Contributing guidelines found.", 1), + ReleaseDrafterProbe.KEY, ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter is configured.", 1) )); ScoreResult result = scoring.apply(plugin); @@ -63,6 +67,61 @@ void shouldScoreOneHundredWithMigratedDocumentationAndContributingGuide() { .isEqualTo(new ScoreResult(DocumentationScoring.KEY, 100, .5f, Set.of(), 1)); } + @Test + void shouldScoreOneHundredEvenWithoutReleaseDrafter() { + final Plugin plugin = mock(Plugin.class); + final DocumentationScoring scoring = getSpy(); + + when(plugin.getDetails()).thenReturn(Map.of( + DocumentationMigrationProbe.KEY, ProbeResult.success(DocumentationMigrationProbe.KEY, "Documentation is located in the plugin repository.", 1), + ContributingGuidelinesProbe.KEY, ProbeResult.success(ContributingGuidelinesProbe.KEY, "Contributing guidelines found.", 1), + ReleaseDrafterProbe.KEY, ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter not is configured.", 1) + )); + + ScoreResult result = scoring.apply(plugin); + assertThat(result) + .isNotNull() + .usingRecursiveComparison().comparingOnlyFields("key", "value") + .isEqualTo(new ScoreResult(DocumentationScoring.KEY, 100, .5f, Set.of(), 1)); + assertThat(result.componentsResults()) + .hasSize(3) + .haveAtLeastOne(new Condition<>( + res -> { + return res.weight() == 0 && res.score() == 0 && res.resolutions().size() == 1 && + res.reasons().contains("Plugin is not using Release Drafter to manage its changelog."); + }, + "Release drafter is not configured" + )); + } + + @Test + void shouldConsiderCDAsConfiguringReleaseDrafter() { + final Plugin plugin = mock(Plugin.class); + final DocumentationScoring scoring = getSpy(); + + when(plugin.getDetails()).thenReturn(Map.of( + DocumentationMigrationProbe.KEY, ProbeResult.success(DocumentationMigrationProbe.KEY, "Documentation is located in the plugin repository.", 1), + ContributingGuidelinesProbe.KEY, ProbeResult.success(ContributingGuidelinesProbe.KEY, "Contributing guidelines found.", 1), + ReleaseDrafterProbe.KEY, ProbeResult.success(ReleaseDrafterProbe.KEY, "Release Drafter not is configured.", 1), + ContinuousDeliveryProbe.KEY, ProbeResult.success(ContinuousDeliveryProbe.KEY, "JEP-229 workflow definition found.", 1) + )); + + ScoreResult result = scoring.apply(plugin); + assertThat(result) + .isNotNull() + .usingRecursiveComparison().comparingOnlyFields("key", "value") + .isEqualTo(new ScoreResult(DocumentationScoring.KEY, 100, .5f, Set.of(), 1)); + assertThat(result.componentsResults()) + .hasSize(3) + .haveAtLeastOne(new Condition<>( + res -> { + return res.weight() == 0 && res.score() == 100 && res.reasons().size() == 1 && + res.reasons().contains("Plugin using Release Drafter because it has CD configured."); + }, + "CD is configured." + )); + } + @Test void shouldScoreEightyWithMigratedDocumentationOnly() { final Plugin plugin = mock(Plugin.class);