From 7974e9fa02859a952c90e6d462e0c22818a02c74 Mon Sep 17 00:00:00 2001 From: Piotr Adamczyk Date: Wed, 19 Aug 2020 16:31:45 +0200 Subject: [PATCH] Make release notes more organized --- .../ci/releasenotes/AppendReleaseNotes.kt | 17 ++---- .../ConventionalCommitFormatter.kt | 20 +++---- .../ci/releasenotes/GenerateChangeLog.kt | 23 +++++--- .../GenerateReleaseNotesCommand.kt | 17 +++--- .../ci/releasenotes/ReleaseNotesWithType.kt | 18 +++++++ .../flank/scripts/release/hub/ReleaseFlank.kt | 3 +- .../flank/scripts/utils/MarkdownFormatter.kt | 2 + .../ci/releasenotes/AppendReleaseNotesTest.kt | 33 +++++++++--- .../ConventionalCommitFormatterTest.kt | 39 +++++++------- .../releasenotes/ReleaseNotesWithTypeTest.kt | 52 +++++++++++++++++++ 10 files changed, 162 insertions(+), 62 deletions(-) create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt create mode 100644 flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithTypeTest.kt diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotes.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotes.kt index 86c64a35f9..d1d720358c 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotes.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotes.kt @@ -1,24 +1,17 @@ package flank.scripts.ci.releasenotes -import flank.scripts.utils.markdownH2 import java.io.File -fun File.appendReleaseNotes(messages: List, releaseTag: String) { +fun File.appendReleaseNotes(releaseNotesWithType: ReleaseNotesWithType, releaseTag: String) { appendToReleaseNotes( - messages = messages, + releaseNotesWithType = releaseNotesWithType, releaseTag = releaseTag ) } -private fun File.appendToReleaseNotes(messages: List, releaseTag: String) { - val currentFileLines = readLines() - val newLines = mutableListOf().apply { - add(releaseTag.markdownH2()) - addAll(messages) - add("") - } - - writeText((newLines + currentFileLines).joinToString(System.lineSeparator()).withNewLineAtTheEnd()) +private fun File.appendToReleaseNotes(releaseNotesWithType: ReleaseNotesWithType, releaseTag: String) { + writeText(releaseNotesWithType.asString(releaseTag).withNewLineAtTheEnd() + + readLines().joinToString(System.lineSeparator()).withNewLineAtTheEnd()) } private fun String.withNewLineAtTheEnd() = plus(System.lineSeparator()) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatter.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatter.kt index ce206653aa..28c14641f1 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatter.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatter.kt @@ -1,14 +1,14 @@ package flank.scripts.ci.releasenotes -import flank.scripts.utils.markdownBold - -fun String.mapPrTitle() = when { - startsWith("feat") -> replaceFirst("feat", "New feature".markdownBold()) - startsWith("fix") -> replaceFirst("fix", "Fix".markdownBold()) - startsWith("docs") -> replaceFirst("docs", "Documentation".markdownBold()) - startsWith("refactor") -> replaceFirst("refactor", "Refactor".markdownBold()) - startsWith("ci") -> replaceFirst("ci", "CI changes".markdownBold()) - startsWith("test") -> replaceFirst("test", "Tests update".markdownBold()) - startsWith("perf") -> replaceFirst("perf", "Performance upgrade".markdownBold()) +fun String.mapPrTitleWithType() = when { + startsWith("feat") -> "Features" to skipConventionalCommitPrefix().capitalize() + startsWith("fix") -> "Bug Fixes" to skipConventionalCommitPrefix().capitalize() + startsWith("docs") -> "Documentation" to skipConventionalCommitPrefix().capitalize() + startsWith("refactor") -> "Refactor" to skipConventionalCommitPrefix().capitalize() + startsWith("ci") -> "CI Changes" to skipConventionalCommitPrefix().capitalize() + startsWith("test") -> "Tests update" to skipConventionalCommitPrefix().capitalize() + startsWith("perf") -> "Performance upgrade" to skipConventionalCommitPrefix().capitalize() else -> null // we do not accept other prefix to have update in release notes } + +private fun String.skipConventionalCommitPrefix() = substring(indexOf(':') + 2) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateChangeLog.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateChangeLog.kt index 8e1e69cdd3..5c9cd2291e 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateChangeLog.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateChangeLog.kt @@ -22,7 +22,7 @@ fun generateReleaseNotes(githubToken: String) = runBlocking { fun generateReleaseNotes( latestReleaseTag: String, githubToken: String -) = getCommitsSha(latestReleaseTag).getReleaseNotes(githubToken) +) = getCommitsSha(latestReleaseTag).getNewReleaseNotes(githubToken) internal fun getCommitsSha(fromTag: String): List { val outputFile = File.createTempFile("sha", ".log") @@ -32,18 +32,27 @@ internal fun getCommitsSha(fromTag: String): List { return outputFile.readLines() } -private fun List.getReleaseNotes(githubToken: String) = runBlocking { +private fun List.getNewReleaseNotes(githubToken: String) = runBlocking { map { sha -> async { getPrDetailsByCommit(sha, githubToken) } } .awaitAll() .filterIsInstance>>() - .map { it.value } - .filter { it.isNotEmpty() } - .mapNotNull { it.first().toReleaseNoteMessage() } + .mapNotNull { it.value.firstOrNull()?.toReleaseNoteMessage() } + .fold(mutableMapOf>()) { grouped, pr -> + grouped.apply { + val (type, message) = pr + appendToType(type, message) + } + } } +private fun MutableMap>.appendToType( + type: String, + message: String +) = apply { (getOrPut(type) { mutableListOf() }).add(message) } + private fun GithubPullRequest.toReleaseNoteMessage() = - title.mapPrTitle()?.let { title -> - "- ${markdownLink(number.toString(), htmlUrl)} $title ${assignees.format()}" + title.mapPrTitleWithType()?.let { (type, title) -> + type to "- ${markdownLink("#$number", htmlUrl)} $title ${assignees.format()}" } private fun List.format() = "(${joinToString { (login, url) -> markdownLink(login, url) }})" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateReleaseNotesCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateReleaseNotesCommand.kt index 496fa47dd7..75f3d56abd 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateReleaseNotesCommand.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GenerateReleaseNotesCommand.kt @@ -4,11 +4,12 @@ import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required +import com.github.kittinunf.result.map import com.github.kittinunf.result.success import flank.scripts.ci.nexttag.generateNextReleaseTag import flank.scripts.github.getLatestReleaseTag -import java.io.File import kotlinx.coroutines.runBlocking +import java.io.File class GenerateReleaseNotesCommand : CliktCommand("Command to append item to release notes", name = "generateReleaseNotes") { @@ -18,12 +19,14 @@ class GenerateReleaseNotesCommand : override fun run() { runBlocking { - getLatestReleaseTag(token).success { - File(releaseNotesFile).appendReleaseNotes( - messages = generateReleaseNotes(it.tag, token), - releaseTag = generateNextReleaseTag(it.tag) - ) - } + getLatestReleaseTag(token) + .map { it.tag } + .success { previousTag -> + File(releaseNotesFile).appendReleaseNotes( + releaseNotesWithType = generateReleaseNotes(previousTag, token), + releaseTag = generateNextReleaseTag(previousTag) + ) + } } } } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt new file mode 100644 index 0000000000..1ddb69e6ad --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt @@ -0,0 +1,18 @@ +package flank.scripts.ci.releasenotes + +import flank.scripts.utils.markdownH2 +import flank.scripts.utils.markdownH3 +import java.lang.StringBuilder + +typealias ReleaseNotesWithType = Map> + +fun ReleaseNotesWithType.asString(headerTag: String) = + StringBuilder(headerTag.markdownH2()) + .appendln() + .apply { + this@asString.forEach { (type, messages) -> + appendln(type.markdownH3()) + messages.forEach { appendln(it) } + } + } + .toString() diff --git a/flank-scripts/src/main/kotlin/flank/scripts/release/hub/ReleaseFlank.kt b/flank-scripts/src/main/kotlin/flank/scripts/release/hub/ReleaseFlank.kt index 0866428785..e3189ac87c 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/release/hub/ReleaseFlank.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/release/hub/ReleaseFlank.kt @@ -1,5 +1,6 @@ package flank.scripts.release.hub +import flank.scripts.ci.releasenotes.ReleaseNotesWithType import flank.scripts.ci.releasenotes.generateReleaseNotes import flank.scripts.utils.markdownH2 import flank.scripts.utils.runCommand @@ -38,7 +39,7 @@ private fun hubStableReleaseCommand(path: String, gitTag: String, token: String) gitTag ) -private fun List.asReleaseBody(tag: String) = +private fun ReleaseNotesWithType.asReleaseBody(tag: String) = StringBuilder(tag.markdownH2()) .appendln() .apply { this@asReleaseBody.forEach { appendln(it) } } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/MarkdownFormatter.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/MarkdownFormatter.kt index 6052e3a38f..565458ca53 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/utils/MarkdownFormatter.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/MarkdownFormatter.kt @@ -5,3 +5,5 @@ fun String.markdownBold() = "**$this**" fun markdownLink(description: String, url: String) = "[$description]($url)" fun String.markdownH2() = "## $this" + +fun String.markdownH3() = "### $this" diff --git a/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotesTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotesTest.kt index 4ddc1f0b1d..3279a937c4 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotesTest.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/AppendReleaseNotesTest.kt @@ -2,6 +2,7 @@ package flank.scripts.ci.releasenotes import com.google.common.truth.Truth.assertThat import flank.scripts.utils.markdownH2 +import flank.scripts.utils.markdownH3 import java.io.File import org.junit.Test @@ -13,12 +14,26 @@ class AppendReleaseNotesTest { val testFile = File.createTempFile("empty", ".md") val tag = "v20.08.0" val expectedHeader = tag.markdownH2() - val messages = listOf( - "- [#11](https://github.com/Flank/flank/pull/11) **New feature**: Tests ([test_of](https://github.com/test_of))", - "- [#12](https://github.com/Flank/flank/pull/12) **New feature**: Tests2 ([test_of](https://github.com/test_of))", - "- [#13](https://github.com/Flank/flank/pull/13) **New feature**: Tests3 ([test_of](https://github.com/test_of))" + val messages = mapOf( + "Features" to listOf( + "- [#1](https://github.com/Flank/flank/pull/1) Tests1 ([test_of](https://github.com/test_of))", + "- [#2](https://github.com/Flank/flank/pull/2) Tests2 ([test_of](https://github.com/test_of))", + "- [#3](https://github.com/Flank/flank/pull/3) Tests3 ([test_of](https://github.com/test_of))" + ), + "Bug fixes" to listOf( + "- [#11](https://github.com/Flank/flank/pull/11) Tests11 ([test_of](https://github.com/test_of))", + "- [#12](https://github.com/Flank/flank/pull/12) Tests12 ([test_of](https://github.com/test_of))", + "- [#13](https://github.com/Flank/flank/pull/13) Tests13 ([test_of](https://github.com/test_of))" + ), + "Documentation" to listOf( + "- [#21](https://github.com/Flank/flank/pull/21) Tests21 ([test_of](https://github.com/test_of))", + "- [#22](https://github.com/Flank/flank/pull/22) Tests22 ([test_of](https://github.com/test_of))", + "- [#23](https://github.com/Flank/flank/pull/23) Tests33 ([test_of](https://github.com/test_of))" + ) ) - val expectedLinesCount = testFile.readLines().size + messages.size + 2 // header + newline + val expectedLinesCount = testFile.readLines().size + + 12 + // values to append + 3 // header + newline + end newline // when testFile.appendReleaseNotes(messages, tag) @@ -26,8 +41,12 @@ class AppendReleaseNotesTest { // verify val lines = testFile.readLines() assertThat(lines.first()).isEqualTo(expectedHeader) - messages.forEachIndexed { index, text -> - assertThat(lines[index + 1]).isEqualTo(text) + var currentLine = 1 + messages.keys.forEach { type -> + assertThat(lines[currentLine++]).isEqualTo(type.markdownH3()) + messages[type]?.forEach { line -> + assertThat(lines[currentLine++]).isEqualTo(line) + } } assertThat(lines.last()).isEmpty() assertThat(lines.size).isEqualTo(expectedLinesCount) diff --git a/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatterTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatterTest.kt index 43809e68b0..4772d51b4f 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatterTest.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ConventionalCommitFormatterTest.kt @@ -6,37 +6,40 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) -class ConventionalCommitFormatterTest(private val mapFrom: String, private val expected: String?) { +class ConventionalCommitFormatterTest( + private val mapFrom: String, + private val expectedType: String?, + private val expectedTitle: String? +) { companion object { @JvmStatic @Parameterized.Parameters(name = "{0} to {1}") fun data() = listOf( - arrayOf("feat: Sample", "New feature"), - arrayOf("fix: Bug", "Fix"), - arrayOf("docs: New", "Documentation"), - arrayOf("refactor: Eveerything", "Refactor"), - arrayOf("ci(cd): actions", "CI changes"), - arrayOf("test(ios): update", "Tests update"), - arrayOf("perf!: 10%", "Performance upgrade"), - arrayOf("style: dd", null), - arrayOf("build: passed", null), - arrayOf("chore: release", null), - arrayOf("nope: nbd", null) + arrayOf("feat: Sample", "Features", "Sample"), + arrayOf("fix: Bug", "Bug Fixes", "Bug"), + arrayOf("docs: New", "Documentation", "New"), + arrayOf("refactor: Eveerything", "Refactor", "Eveerything"), + arrayOf("ci(cd): actions", "CI Changes", "Actions"), + arrayOf("test(ios): update", "Tests update", "Update"), + arrayOf("perf!: 10%", "Performance upgrade", "10%"), + arrayOf("style: dd", null, null), + arrayOf("build: passed", null, null), + arrayOf("chore: release", null, null), + arrayOf("nope: nbd", null, null) ) } @Test fun `Should properly map`() { - // given - val expected = expected?.let { "**$it**" } - // when - val actual = mapFrom.mapPrTitle() + val actual = mapFrom.mapPrTitleWithType() // then - if (expected != null) { - assertThat(actual).startsWith(expected) + if (expectedType != null) { + val (type, title) = actual!! + assertThat(type).isEqualTo(expectedType) + assertThat(title).startsWith(expectedTitle) } else { assertThat(actual).isNull() } diff --git a/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithTypeTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithTypeTest.kt new file mode 100644 index 0000000000..32852d5a4a --- /dev/null +++ b/flank-scripts/src/test/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithTypeTest.kt @@ -0,0 +1,52 @@ +package flank.scripts.ci.releasenotes + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ReleaseNotesWithTypeTest { + + @Test + fun `Should properly format release notes with type`() { + // given + val testTag = "TAG" + val expected = """ + ## TAG + ### Features + - [#1](https://github.com/Flank/flank/pull/1) Tests1 ([test_of](https://github.com/test_of)) + - [#2](https://github.com/Flank/flank/pull/2) Tests2 ([test_of](https://github.com/test_of)) + - [#3](https://github.com/Flank/flank/pull/3) Tests3 ([test_of](https://github.com/test_of)) + ### Bug fixes + - [#11](https://github.com/Flank/flank/pull/11) Tests11 ([test_of](https://github.com/test_of)) + - [#12](https://github.com/Flank/flank/pull/12) Tests12 ([test_of](https://github.com/test_of)) + - [#13](https://github.com/Flank/flank/pull/13) Tests13 ([test_of](https://github.com/test_of)) + ### Documentation + - [#21](https://github.com/Flank/flank/pull/21) Tests21 ([test_of](https://github.com/test_of)) + - [#22](https://github.com/Flank/flank/pull/22) Tests22 ([test_of](https://github.com/test_of)) + - [#23](https://github.com/Flank/flank/pull/23) Tests33 ([test_of](https://github.com/test_of)) + + """.trimIndent() + val releaseNotesWithType = mapOf( + "Features" to listOf( + "- [#1](https://github.com/Flank/flank/pull/1) Tests1 ([test_of](https://github.com/test_of))", + "- [#2](https://github.com/Flank/flank/pull/2) Tests2 ([test_of](https://github.com/test_of))", + "- [#3](https://github.com/Flank/flank/pull/3) Tests3 ([test_of](https://github.com/test_of))" + ), + "Bug fixes" to listOf( + "- [#11](https://github.com/Flank/flank/pull/11) Tests11 ([test_of](https://github.com/test_of))", + "- [#12](https://github.com/Flank/flank/pull/12) Tests12 ([test_of](https://github.com/test_of))", + "- [#13](https://github.com/Flank/flank/pull/13) Tests13 ([test_of](https://github.com/test_of))" + ), + "Documentation" to listOf( + "- [#21](https://github.com/Flank/flank/pull/21) Tests21 ([test_of](https://github.com/test_of))", + "- [#22](https://github.com/Flank/flank/pull/22) Tests22 ([test_of](https://github.com/test_of))", + "- [#23](https://github.com/Flank/flank/pull/23) Tests33 ([test_of](https://github.com/test_of))" + ) + ) + + // when + val actual = releaseNotesWithType.asString(testTag) + + // then + assertEquals(expected, actual) + } +}