From 12cc70340e94c18d0ab48ecd4b0bddb20ff3a627 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Sun, 19 Oct 2025 17:26:28 +0200 Subject: [PATCH 1/2] feat: hyperlink trailing pr mention in commit summary --- .../papermc/fill/discord/DiscordNotifier.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/papermc/fill/discord/DiscordNotifier.java b/src/main/java/io/papermc/fill/discord/DiscordNotifier.java index 52e467a..fe20e55 100644 --- a/src/main/java/io/papermc/fill/discord/DiscordNotifier.java +++ b/src/main/java/io/papermc/fill/discord/DiscordNotifier.java @@ -49,6 +49,8 @@ import java.util.Locale; import java.util.Objects; import java.util.OptionalInt; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -60,6 +62,9 @@ @ConditionalOnProperty("app.discord.token") @NullMarked public class DiscordNotifier implements BuildPublishListener { + + private static final Pattern TRAILING_PR = Pattern.compile(" \\(#(\\d+)\\)\\z"); + private final ApplicationDiscordProperties properties; private final BuildRepository builds; @@ -147,7 +152,7 @@ private Container createContent(final ProjectEntity project, final Version versi repository.name(), commit.sha() ), - commit.summary() + hyperlinkPrMention(commit.summary(), repository.owner(), repository.name()) )).collect(Collectors.joining("\n")) )); }, switch (build.channel()) { @@ -194,4 +199,17 @@ private Container createContent(final ProjectEntity project, final Version versi private static Emoji createEmoji(final ApplicationDiscordProperties.Emojis.Emoji emoji) { return CustomEmoji.of(emoji.id(), emoji.name(), false); } + + private static String hyperlinkPrMention(final String commitSummary, final String repoOwner, final String repoName) { + if (commitSummary.isEmpty()) return commitSummary; + + final Matcher matcher = TRAILING_PR.matcher(commitSummary); + if (!matcher.find()) return commitSummary; + + final String prNumber = matcher.group(1); + + return matcher.replaceFirst( + String.format(" ([#%s](https://github.com/%s/%s/pull/%s))", + prNumber, repoOwner, repoName, prNumber)); + } } From 0248a56d1a5ff4c3fed59f647e4cedf556d88d68 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Sat, 25 Oct 2025 10:58:22 +0200 Subject: [PATCH 2/2] chore: add unit tests extract PR hyperlinking logic to util class to simplify testing --- .../papermc/fill/discord/DiscordNotifier.java | 21 +------- .../util/discord/PrToMdHyperLinkUtil.java | 32 +++++++++++ .../util/discord/PrToMdHyperLinkUtilTest.java | 54 +++++++++++++++++++ 3 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/papermc/fill/util/discord/PrToMdHyperLinkUtil.java create mode 100644 src/test/java/io/papermc/fill/model/util/discord/PrToMdHyperLinkUtilTest.java diff --git a/src/main/java/io/papermc/fill/discord/DiscordNotifier.java b/src/main/java/io/papermc/fill/discord/DiscordNotifier.java index fe20e55..251ee29 100644 --- a/src/main/java/io/papermc/fill/discord/DiscordNotifier.java +++ b/src/main/java/io/papermc/fill/discord/DiscordNotifier.java @@ -43,14 +43,13 @@ import io.papermc.fill.util.BuildPublishListener; import io.papermc.fill.util.discord.Components; import io.papermc.fill.util.discord.DiscordNotificationChannel; +import io.papermc.fill.util.discord.PrToMdHyperLinkUtil; import io.papermc.fill.util.git.GitRepository; import java.net.URI; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.OptionalInt; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -62,9 +61,6 @@ @ConditionalOnProperty("app.discord.token") @NullMarked public class DiscordNotifier implements BuildPublishListener { - - private static final Pattern TRAILING_PR = Pattern.compile(" \\(#(\\d+)\\)\\z"); - private final ApplicationDiscordProperties properties; private final BuildRepository builds; @@ -152,7 +148,7 @@ private Container createContent(final ProjectEntity project, final Version versi repository.name(), commit.sha() ), - hyperlinkPrMention(commit.summary(), repository.owner(), repository.name()) + PrToMdHyperLinkUtil.hyperlinkTrailingPrMention(commit.summary(), repository.owner(), repository.name()) )).collect(Collectors.joining("\n")) )); }, switch (build.channel()) { @@ -199,17 +195,4 @@ private Container createContent(final ProjectEntity project, final Version versi private static Emoji createEmoji(final ApplicationDiscordProperties.Emojis.Emoji emoji) { return CustomEmoji.of(emoji.id(), emoji.name(), false); } - - private static String hyperlinkPrMention(final String commitSummary, final String repoOwner, final String repoName) { - if (commitSummary.isEmpty()) return commitSummary; - - final Matcher matcher = TRAILING_PR.matcher(commitSummary); - if (!matcher.find()) return commitSummary; - - final String prNumber = matcher.group(1); - - return matcher.replaceFirst( - String.format(" ([#%s](https://github.com/%s/%s/pull/%s))", - prNumber, repoOwner, repoName, prNumber)); - } } diff --git a/src/main/java/io/papermc/fill/util/discord/PrToMdHyperLinkUtil.java b/src/main/java/io/papermc/fill/util/discord/PrToMdHyperLinkUtil.java new file mode 100644 index 0000000..1ad28a6 --- /dev/null +++ b/src/main/java/io/papermc/fill/util/discord/PrToMdHyperLinkUtil.java @@ -0,0 +1,32 @@ +package io.papermc.fill.util.discord; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class PrToMdHyperLinkUtil { + private static final Pattern TRAILING_PR = Pattern.compile(" \\(#(\\d+)\\)\\z"); + + private PrToMdHyperLinkUtil() { + } + + + public static String hyperlinkTrailingPrMention(final String commitSummary, final String repoOwner, final String repoName) { + Objects.requireNonNull(commitSummary); + Objects.requireNonNull(repoOwner); + Objects.requireNonNull(repoName); + + if (commitSummary.isEmpty()) return commitSummary; + + final Matcher matcher = TRAILING_PR.matcher(commitSummary); + if (!matcher.find()) return commitSummary; + + final String prNumber = matcher.group(1); + + return matcher.replaceFirst( + String.format(" ([#%s](https://github.com/%s/%s/pull/%s))", + prNumber, repoOwner, repoName, prNumber)); + } +} diff --git a/src/test/java/io/papermc/fill/model/util/discord/PrToMdHyperLinkUtilTest.java b/src/test/java/io/papermc/fill/model/util/discord/PrToMdHyperLinkUtilTest.java new file mode 100644 index 0000000..e34ca0e --- /dev/null +++ b/src/test/java/io/papermc/fill/model/util/discord/PrToMdHyperLinkUtilTest.java @@ -0,0 +1,54 @@ +package io.papermc.fill.model.util.discord; + +import io.papermc.fill.util.discord.PrToMdHyperLinkUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PrToMdHyperLinkUtilTest { + @ParameterizedTest + @ValueSource(strings = { + "no mention", + "(#36) not trailing", + "missing space(#72)", + "not in brackets #72", + "" + }) + void returnsUnchangedForInvalidCases(String commitSummary) { + String actual = PrToMdHyperLinkUtil.hyperlinkTrailingPrMention(commitSummary, "myOrg", "myRepo"); + assertEquals(commitSummary, actual); + } + + @ParameterizedTest + @CsvSource({ + "Rework broken xyz (#123), myOrg, myRepo, Rework broken xyz ([#123](https://github.com/myOrg/myRepo/pull/123))", + "Don't run player loot table for spectators (#11801), PaperMC, Paper, Don't run player loot table for spectators ([#11801](https://github.com/PaperMC/Paper/pull/11801))", + "feat: add new xyz API (#12), myOrg, myRepo, feat: add new xyz API ([#12](https://github.com/myOrg/myRepo/pull/12))", + "Fix (#1234) via xyz (#4125), myOrg, myRepo, Fix (#1234) via xyz ([#4125](https://github.com/myOrg/myRepo/pull/4125))" + }) + void replacesTrailingPrMentionWithMarkdownLink(String commitSummary, String repoOwner, String repoName, String expected) { + String actual = PrToMdHyperLinkUtil.hyperlinkTrailingPrMention(commitSummary, repoOwner, repoName); + assertEquals(expected, actual); + } + + @ParameterizedTest + @CsvSource({ + "commitSummary, null, myOrg, myRepo", + "repoOwner, working summary (#1), null, myRepo", + "repoName, working summary (#1), myOrg, null" + }) + @SuppressWarnings("DataFlowIssue") + void nullParameterThrowsNPE(String parameter, String commitSummary, String repoOwner, String repoName) { + assertThrows(NullPointerException.class, () -> + PrToMdHyperLinkUtil.hyperlinkTrailingPrMention( + "null".equals(commitSummary) ? null : commitSummary, + "null".equals(repoOwner) ? null : repoOwner, + "null".equals(repoName) ? null : repoName + ), + () -> "Expected NullPointerException for parameter " + parameter + ); + } +}