From 6b15137b1df781fbbd707f876ec22bad53f4ee3d Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 12:42:36 +0200 Subject: [PATCH 01/19] #92 - Add support for more characters as markdown list rows - Implemented support for + and * characters. --- release_notes_generator/model/record.py | 14 +++++++------- release_notes_generator/utils/constants.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index c14f4b90..6e1e23a5 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -31,8 +31,7 @@ PR_STATE_CLOSED, ISSUE_STATE_CLOSED, ISSUE_STATE_OPEN, - RELEASE_NOTE_DETECTION_PATTERN, - RELEASE_NOTE_LINE_MARK, + RELEASE_NOTE_DETECTION_PATTERN, RELEASE_NOTE_LINE_MARKS, ) from release_notes_generator.utils.pull_reuqest_utils import extract_issue_numbers_from_body @@ -129,12 +128,12 @@ def labels(self) -> list[str]: # TODO in Issue named 'Configurable regex-based Release note detection in the PR body' # - 'Release notest:' as detection pattern default - can be defined by user # - '-' as leading line mark for each release note to be used - def get_rls_notes(self, detection_pattern=RELEASE_NOTE_DETECTION_PATTERN, line_mark=RELEASE_NOTE_LINE_MARK) -> str: + def get_rls_notes(self, detection_pattern=RELEASE_NOTE_DETECTION_PATTERN, line_marks=RELEASE_NOTE_LINE_MARKS) -> str: """ Gets the release notes of the record. @param detection_pattern: The detection pattern to use. - @param line_mark: The line mark to use. + @param line_marks: The line marks to use. @return: The release notes of the record as a string. """ release_notes = "" @@ -150,8 +149,8 @@ def get_rls_notes(self, detection_pattern=RELEASE_NOTE_DETECTION_PATTERN, line_m continue if inside_release_notes: - if line.startswith(line_mark): - release_notes += f" {line.strip()}\n" + if line.strip()[0] in line_marks: + release_notes += f" {line.rstrip()}\n" else: break @@ -164,7 +163,8 @@ def contains_release_notes(self) -> bool: if self.__is_release_note_detected: return self.__is_release_note_detected - if RELEASE_NOTE_LINE_MARK in self.get_rls_notes(): + rls_notes = self.get_rls_notes() + if any(mark in rls_notes for mark in RELEASE_NOTE_LINE_MARKS): self.__is_release_note_detected = True return self.__is_release_note_detected diff --git a/release_notes_generator/utils/constants.py b/release_notes_generator/utils/constants.py index 5d2bcdef..1b4fb026 100644 --- a/release_notes_generator/utils/constants.py +++ b/release_notes_generator/utils/constants.py @@ -50,7 +50,7 @@ # Release notes comment constants RELEASE_NOTE_DETECTION_PATTERN = "Release notes:" -RELEASE_NOTE_LINE_MARK = "-" +RELEASE_NOTE_LINE_MARKS = ['-', '*', '+'] # Service chapters titles CLOSED_ISSUES_WITHOUT_PULL_REQUESTS: str = "Closed Issues without Pull Request ⚠️" From 869a6f07b3b97e838156137cede04c633934a14e Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 13:48:21 +0200 Subject: [PATCH 02/19] - Applied black. --- release_notes_generator/model/record.py | 7 +++++-- release_notes_generator/utils/constants.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index 6e1e23a5..98ba4ba7 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -31,7 +31,8 @@ PR_STATE_CLOSED, ISSUE_STATE_CLOSED, ISSUE_STATE_OPEN, - RELEASE_NOTE_DETECTION_PATTERN, RELEASE_NOTE_LINE_MARKS, + RELEASE_NOTE_DETECTION_PATTERN, + RELEASE_NOTE_LINE_MARKS, ) from release_notes_generator.utils.pull_reuqest_utils import extract_issue_numbers_from_body @@ -128,7 +129,9 @@ def labels(self) -> list[str]: # TODO in Issue named 'Configurable regex-based Release note detection in the PR body' # - 'Release notest:' as detection pattern default - can be defined by user # - '-' as leading line mark for each release note to be used - def get_rls_notes(self, detection_pattern=RELEASE_NOTE_DETECTION_PATTERN, line_marks=RELEASE_NOTE_LINE_MARKS) -> str: + def get_rls_notes( + self, detection_pattern=RELEASE_NOTE_DETECTION_PATTERN, line_marks=RELEASE_NOTE_LINE_MARKS + ) -> str: """ Gets the release notes of the record. diff --git a/release_notes_generator/utils/constants.py b/release_notes_generator/utils/constants.py index 1b4fb026..36bc4115 100644 --- a/release_notes_generator/utils/constants.py +++ b/release_notes_generator/utils/constants.py @@ -50,7 +50,7 @@ # Release notes comment constants RELEASE_NOTE_DETECTION_PATTERN = "Release notes:" -RELEASE_NOTE_LINE_MARKS = ['-', '*', '+'] +RELEASE_NOTE_LINE_MARKS = ["-", "*", "+"] # Service chapters titles CLOSED_ISSUES_WITHOUT_PULL_REQUESTS: str = "Closed Issues without Pull Request ⚠️" From ce33aaa1ebfd92138d5d6c43066cad5bc5f5fa86 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 14:33:10 +0200 Subject: [PATCH 03/19] - Improvement of unit tests. - Update of README.md --- README.md | 30 ++++++++++--------- release_notes_generator/model/record.py | 4 ++- tests/conftest.py | 2 +- tests/release_notes/model/test_record.py | 10 ++++--- .../test_release_notes_builder.py | 4 +++ 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0ed47c7e..adafc154 100644 --- a/README.md +++ b/README.md @@ -171,25 +171,27 @@ Add the following step to your GitHub workflow (in example are used non-default ## Features ### Built-in #### Release Notes Extraction Process +This feature searches for release notes in the description of GitHub pull requests, making it easier for maintainers to track changes and updates. +- **Format:** + - The release notes section have to begin with the title `Release Notes:`, followed by the release notes in bullet points. [See Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). + - If no release notes line is detected under the Release notes: "title," no release notes will be printed in the output. +- **Example:** + - Here are examples of how to structure the release notes (case-sensitive): +``` +Release Notes: +- This update introduces a new caching mechanism that improves performance by 20%. -This action requires that your GitHub issues include comments with specific release notes. Here's how it works: +Release Notes: +* This update introduces a new caching mechanism that improves performance by 20%. -**Extraction Method**: -- The action scans through comments on each closed issue since the last release. It identifies comments that follow the specified format and extracts the content as part of the release notes. -- The time considered for the previous release is based on its creation time. This means that the action will look for issues closed after the creation time of the most recent release to ensure that all relevant updates since that release are included. +Release Notes: ++ This update introduces a new caching mechanism that improves performance by 20%. -**Comment Format** -- For an issue's contributions to be included in the release notes, it must contain a comment starting with "Release Notes" followed by the note content. This comment is typically added by the contributors. -- Here is an example of the content for a 'Release Notes' string, which is not case-sensitive: -``` -Release Notes -- This update introduces a new caching mechanism that improves performance by 20%. ``` -- Using `-` as a bullet point for each note is the best practice. The Markdown parser will automatically convert it to a list. -- These comments are not required for action functionality. If an issue does not contain a "Release Notes" comment, it will be marked accordingly in the release notes. This helps maintainers quickly identify which issues need attention for documentation. + - The extraction process supports all three types of bullet points: `-`, `*`, and `+`, and their combinations even when mixed. (It is not recommended to mix them.) +- **Best Practice:** Select one character from `-`, `*`, `+` for bullet points. The Markdown parser will automatically format them as a list. +- **Optional:** Usage of release notes is not mandatory for this GH action. -#### Contributors Mention -Along with the release note content, the action also gathers a list of contributors for each issue. This includes issue assignees and authors of linked pull requests' commits, providing acknowledgment for their contributions in the release notes. #### Handling Multiple PRs If an issue is linked to multiple PRs, the action fetches and aggregates contributions from all linked PRs. diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index 98ba4ba7..ad1805ff 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -25,6 +25,7 @@ from github.PullRequest import PullRequest from github.Repository import Repository from github.Commit import Commit +from scipy.stats import tmean from release_notes_generator.action_inputs import ActionInputs from release_notes_generator.utils.constants import ( @@ -152,7 +153,8 @@ def get_rls_notes( continue if inside_release_notes: - if line.strip()[0] in line_marks: + tmp = line.strip() + if len(tmp) > 0 and tmp[0] in line_marks: release_notes += f" {line.rstrip()}\n" else: break diff --git a/tests/conftest.py b/tests/conftest.py index cdd09f7e..d65be12a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -174,7 +174,7 @@ def mock_issue_closed_i1_bug(mocker): def mock_pull_closed(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Release notes:\n- Fixed bug\n- Improved performance\n" + pull.body = "Release notes:\n- Fixed bug\n- Improved performance\n+ More nice code\n * Awesome architecture" pull.url = "http://example.com/pull/123" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" diff --git a/tests/release_notes/model/test_record.py b/tests/release_notes/model/test_record.py index 7c46fcc3..23e98baf 100644 --- a/tests/release_notes/model/test_record.py +++ b/tests/release_notes/model/test_record.py @@ -66,7 +66,7 @@ def test_record_properties_authors_contributors(record_with_no_issue_one_pull_cl def test_get_rls_notes(record_with_no_issue_one_pull_closed): - expected_notes = " - Fixed bug\n - Improved performance" + expected_notes = " - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" assert record_with_no_issue_one_pull_closed.get_rls_notes() == expected_notes @@ -137,20 +137,22 @@ def test_register_commit_failure(record_with_no_issue_one_pull_closed, caplog, m def test_to_chapter_row_with_pull(record_with_no_issue_one_pull_closed): - expected_row = "PR: #123 _Fixed bug_\n - Fixed bug\n - Improved performance" + expected_row = "PR: #123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" assert expected_row == record_with_no_issue_one_pull_closed.to_chapter_row() def test_to_chapter_row_with_pull_no_pr_prefix(record_with_no_issue_one_pull_closed, mocker): mocker.patch("release_notes_generator.builder.ActionInputs.get_row_format_link_pr", return_value=False) - expected_row = "#123 _Fixed bug_\n - Fixed bug\n - Improved performance" + expected_row = "#123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" assert expected_row == record_with_no_issue_one_pull_closed.to_chapter_row() def test_to_chapter_row_with_issue(record_with_issue_closed_one_pull): expected_row = """#121 _Fix the bug_ in [#123](https://github.com/org/repo/pull/123) - Fixed bug - - Improved performance""" + - Improved performance + + More nice code + * Awesome architecture""" assert expected_row == record_with_issue_closed_one_pull.to_chapter_row() diff --git a/tests/release_notes/test_release_notes_builder.py b/tests/release_notes/test_release_notes_builder.py index 9002b719..8959d87a 100644 --- a/tests/release_notes/test_release_notes_builder.py +++ b/tests/release_notes/test_release_notes_builder.py @@ -196,6 +196,8 @@ def __init__(self, name): - PR: #123 _Fixed bug_ - Fixed bug - Improved performance + + More nice code + * Awesome architecture #### Full Changelog http://example.com/changelog @@ -275,6 +277,8 @@ def __init__(self, name): - #121 _Fix the bug_ in [#123](https://github.com/org/repo/pull/123) - Fixed bug - Improved performance + + More nice code + * Awesome architecture #### Full Changelog http://example.com/changelog From d4121ae193e7ad49eb6153c166f773e37b3560a8 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 14:35:37 +0200 Subject: [PATCH 04/19] - Update of README.md. Fixed comment mentions. --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index adafc154..265ac47e 100644 --- a/README.md +++ b/README.md @@ -192,13 +192,9 @@ Release Notes: - **Best Practice:** Select one character from `-`, `*`, `+` for bullet points. The Markdown parser will automatically format them as a list. - **Optional:** Usage of release notes is not mandatory for this GH action. - #### Handling Multiple PRs If an issue is linked to multiple PRs, the action fetches and aggregates contributions from all linked PRs. -#### No Release Notes Found -If no valid "Release Notes" comment is found in an issue, it will be marked accordingly. This helps maintainers quickly identify which issues need attention for documentation. - ### Select start date for closed issues and PRs By set **published-at** to true the action will use the `published-at` timestamp of the latest release as the reference point for searching closed issues and PRs, instead of the `created-at` date. If first release, repository creation date is used. @@ -219,7 +215,7 @@ The action includes four specific warning chapters to highlight potential areas - **Importance**: Ensures all issues are categorized correctly according to the project's classification system. It aids in organizing release notes into predefined chapters effectively. - **_Closed Issues Without Release Notes_** - - **Purpose**: Identifies issues that do not contain a "Release Notes" comment. + - **Purpose**: Identifies pull requests which do not contain a "Release Notes" section in description. - **Importance**: Ensures that all significant changes are properly documented in the release notes, enhancing the completeness and usefulness of the release information provided to end-users. - **_Merged PRs Without Linked Issue_** @@ -458,8 +454,8 @@ We defined chapters for our GH actions this way: Then in chapters `New Features 🎉` and `Bugfixes 🛠` will be duplicated lines for this issue. When mentioned second+ times then **[Duplicate]** prefix will be visible. In the `New Features 🎉` chapter will be mentioned this issue once only. -### What will happen when the issue contains multiple "Release Notes" comments? -All issue comments are checked for presence of `Release Notes` string. All detected release notes are collected printed under issue. +### What will happen when the pull request contains multiple "Release Notes" sections? +Only the first one will be used. ### What will happen when Merged PR is linked to open issues? The PR will be mentioned in warning chapter **Merged PRs Linked to Open Issue**. From 59e28950eee26afc93b01717bc61a6f811550702 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 14:39:52 +0200 Subject: [PATCH 05/19] - Remove not used import. --- release_notes_generator/model/record.py | 1 - 1 file changed, 1 deletion(-) diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index ad1805ff..f53236f7 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -25,7 +25,6 @@ from github.PullRequest import PullRequest from github.Repository import Repository from github.Commit import Commit -from scipy.stats import tmean from release_notes_generator.action_inputs import ActionInputs from release_notes_generator.utils.constants import ( From f51297814a7768870189bb79f3691730bc1e11dc Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Fri, 11 Oct 2024 14:41:58 +0200 Subject: [PATCH 06/19] - Apply black to fix PR check. --- tests/release_notes/model/test_record.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/release_notes/model/test_record.py b/tests/release_notes/model/test_record.py index 23e98baf..14c2b9db 100644 --- a/tests/release_notes/model/test_record.py +++ b/tests/release_notes/model/test_record.py @@ -137,13 +137,17 @@ def test_register_commit_failure(record_with_no_issue_one_pull_closed, caplog, m def test_to_chapter_row_with_pull(record_with_no_issue_one_pull_closed): - expected_row = "PR: #123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" + expected_row = ( + "PR: #123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" + ) assert expected_row == record_with_no_issue_one_pull_closed.to_chapter_row() def test_to_chapter_row_with_pull_no_pr_prefix(record_with_no_issue_one_pull_closed, mocker): mocker.patch("release_notes_generator.builder.ActionInputs.get_row_format_link_pr", return_value=False) - expected_row = "#123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" + expected_row = ( + "#123 _Fixed bug_\n - Fixed bug\n - Improved performance\n + More nice code\n * Awesome architecture" + ) assert expected_row == record_with_no_issue_one_pull_closed.to_chapter_row() From 4f2ccb224326683d1805fa8b819e2fcd9453db46 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Mon, 14 Oct 2024 08:56:07 +0200 Subject: [PATCH 07/19] #91 - Placeholder {number} does not # before Issue or PR number - Removed the need to provide # in template of row - for record number. - Update of pull-request to GitHub link type. Removed Markdown format of link. --- README.md | 6 +++--- action.yml | 8 ++++---- release_notes_generator/action_inputs.py | 4 ++-- release_notes_generator/model/record.py | 8 ++++---- tests/release_notes/model/test_record.py | 6 +++--- tests/release_notes/test_release_notes_builder.py | 14 +++++++------- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 265ac47e..a8df4fdf 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,12 @@ Generate Release Notes action is dedicated to enhance the quality and organizati ### `row-format-issue` - **Description**: The format of the row for the issue in the release notes. The format can contain placeholders for the issue `number`, `title`, and issues `pull-requests`. The placeholders are case-sensitive. - **Required**: No -- **Default**: `#{number} _{title}_ in {pull-requests}"` +- **Default**: `"{number} _{title}_ in {pull-requests}"` ### `row-format-pr` -- **Description**: The format of the row for the PR in the release notes. The format can contain placeholders for the PR `number`, `title`, and PR `pull-requests`. The placeholders are case-sensitive. +- **Description**: The format of the row for the PR in the release notes. The format can contain placeholders for the PR `number`, and `title`. The placeholders are case-sensitive. - **Required**: No -- **Default**: `#{number} _{title}_"` +- **Default**: `"{number} _{title}_"` ### `row-format-link-pr` - **Description**: If defined `true`, the PR row will begin with a `"PR: "` string. Otherwise, no prefix will be added. diff --git a/action.yml b/action.yml index c531a99a..1148a14e 100644 --- a/action.yml +++ b/action.yml @@ -56,13 +56,13 @@ inputs: required: false default: 'false' row-format-issue: - description: 'Format of the issue row in the release notes. Available placeholders: {link}, {title}, {pull-requests}. Placeholders are case-insensitive.' + description: 'Format of the issue row in the release notes. Available placeholders: {number}, {title}, {pull-requests}. Placeholders are case-insensitive.' required: false - default: '#{number} _{title}_ in {pull-requests}' + default: '{number} _{title}_ in {pull-requests}' row-format-pr: - description: 'Format of the pr row in the release notes. Available placeholders: {link}, {title}, {pull-requests}. Placeholders are case-insensitive.' + description: 'Format of the pr row in the release notes. Available placeholders: {number}, {title}, {pull-requests}. Placeholders are case-insensitive.' required: false - default: '#{number} _{title}_' + default: '{number} _{title}_' row-format-link-pr: description: 'Add prefix "PR:" before link to PR when not linked an Issue.' required: false diff --git a/release_notes_generator/action_inputs.py b/release_notes_generator/action_inputs.py index 519b689d..a650c3bb 100644 --- a/release_notes_generator/action_inputs.py +++ b/release_notes_generator/action_inputs.py @@ -167,14 +167,14 @@ def get_row_format_issue() -> str: """ Get the issue row format for the release notes. """ - return get_action_input(ROW_FORMAT_ISSUE, "#{number} _{title}_ in {pull-requests}").strip() + return get_action_input(ROW_FORMAT_ISSUE, "{number} _{title}_ in {pull-requests}").strip() @staticmethod def get_row_format_pr() -> str: """ Get the pr row format for the release notes. """ - return get_action_input(ROW_FORMAT_PR, "#{number} _{title}_").strip() + return get_action_input(ROW_FORMAT_PR, "{number} _{title}_").strip() @staticmethod def get_row_format_link_pr() -> bool: diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index f53236f7..ec2d7bdc 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -211,8 +211,8 @@ def pr_links(self) -> Optional[str]: if len(self.__pulls) == 0: return None - template = "[#{number}](https://github.com/{full_name}/pull/{number})" - res = [template.format(number=pull.number, full_name=self.__repo.full_name) for pull in self.__pulls] + template = "#{number}" + res = [template.format(number=pull.number) for pull in self.__pulls] return ", ".join(res) @@ -280,7 +280,7 @@ def to_chapter_row(self) -> str: if self.__gh_issue is None: p = self.__pulls[0] - format_values["number"] = p.number + format_values["number"] = f"#{p.number}" format_values["title"] = p.title format_values["authors"] = self.authors if self.authors is not None else "" format_values["contributors"] = self.contributors if self.contributors is not None else "" @@ -289,7 +289,7 @@ def to_chapter_row(self) -> str: row = f"{row_prefix}{pr_prefix}" + ActionInputs.get_row_format_pr().format(**format_values) else: - format_values["number"] = self.__gh_issue.number + format_values["number"] = f"#{self.__gh_issue.number}" format_values["title"] = self.__gh_issue.title format_values["pull-requests"] = self.pr_links if len(self.__pulls) > 0 else "" format_values["authors"] = self.authors if self.authors is not None else "" diff --git a/tests/release_notes/model/test_record.py b/tests/release_notes/model/test_record.py index 14c2b9db..d7df6ee3 100644 --- a/tests/release_notes/model/test_record.py +++ b/tests/release_notes/model/test_record.py @@ -100,7 +100,7 @@ def test_pr_contains_issue_mentions(record_with_no_issue_one_pull_closed, mocker def test_pr_links(record_with_no_issue_one_pull_closed): - expected_links = "[#123](https://github.com/org/repo/pull/123)" + expected_links = "#123" assert record_with_no_issue_one_pull_closed.pr_links == expected_links @@ -152,7 +152,7 @@ def test_to_chapter_row_with_pull_no_pr_prefix(record_with_no_issue_one_pull_clo def test_to_chapter_row_with_issue(record_with_issue_closed_one_pull): - expected_row = """#121 _Fix the bug_ in [#123](https://github.com/org/repo/pull/123) + expected_row = """#121 _Fix the bug_ in #123 - Fixed bug - Improved performance + More nice code @@ -166,7 +166,7 @@ def test_to_chapter_row_with_pull_no_rls_notes(record_with_no_issue_one_pull_clo def test_to_chapter_row_with_issue_no_rls_notes(record_with_issue_closed_one_pull_no_rls_notes): - expected_row = "#121 _Fix the bug_ in [#123](https://github.com/org/repo/pull/123)" + expected_row = "#121 _Fix the bug_ in #123" assert expected_row == record_with_issue_closed_one_pull_no_rls_notes.to_chapter_row() diff --git a/tests/release_notes/test_release_notes_builder.py b/tests/release_notes/test_release_notes_builder.py index 8959d87a..aeb57a55 100644 --- a/tests/release_notes/test_release_notes_builder.py +++ b/tests/release_notes/test_release_notes_builder.py @@ -138,7 +138,7 @@ def __init__(self, name): RELEASE_NOTES_NO_DATA_NO_EMPTY_CHAPTERS = RELEASE_NOTES_NO_DATA_NO_WARNING_NO_EMPTY_CHAPTERS RELEASE_NOTES_DATA_CUSTOM_CHAPTERS_ONE_LABEL = """### Chapter 1 🛠 -- #122 _I1+bug_ in [#101](https://github.com/org/repo/pull/101), [#102](https://github.com/org/repo/pull/102) +- #122 _I1+bug_ in #101, #102 - PR 101 1st release note - PR 101 2nd release note - PR 102 1st release note @@ -149,7 +149,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_CUSTOM_CHAPTERS_MORE_LABELS_DUPLICITY_REDUCTION_ON = """### Chapter 1 🛠 -- #122 _I1+bug-enhancement_ in [#101](https://github.com/org/repo/pull/101), [#102](https://github.com/org/repo/pull/102) +- #122 _I1+bug-enhancement_ in #101, #102 - PR 101 1st release note - PR 101 2nd release note - PR 102 1st release note @@ -160,7 +160,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_CUSTOM_CHAPTERS_MORE_LABELS_DUPLICITY_REDUCTION_OFF = """### New Features 🎉 -- #1 _I1+0PR+2L-bug-enhancement_ in [#101](https://github.com/org/repo/pull/101), [#102](https://github.com/org/repo/pull/102) +- #1 _I1+0PR+2L-bug-enhancement_ in #101, #102 - PR 101 1st release note - PR 101 2nd release note - PR 102 1st release note @@ -204,7 +204,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_SERVICE_CHAPTERS_OPEN_ISSUE_AND_MERGED_PR_NO_USER_LABELS = """### Merged PRs Linked to 'Not Closed' Issue ⚠️ -- #122 _I1 open_ in [#101](https://github.com/org/repo/pull/101), [#102](https://github.com/org/repo/pull/102) +- #122 _I1 open_ in #101, #102 - PR 101 1st release note - PR 101 2nd release note - PR 102 1st release note @@ -233,7 +233,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_CLOSED_ISSUE_WITH_PR_WITHOUT_USER_LABELS = """### Closed Issues without User Defined Labels ⚠️ -- #122 _I1_ in [#101](https://github.com/org/repo/pull/101), [#102](https://github.com/org/repo/pull/102) +- #122 _I1_ in #101, #102 - PR 101 1st release note - PR 101 2nd release note - PR 102 1st release note @@ -274,7 +274,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_CLOSED_ISSUE_WITH_MERGED_PRS_WITHOUT_USER_LABELS = """### Closed Issues without User Defined Labels ⚠️ -- #121 _Fix the bug_ in [#123](https://github.com/org/repo/pull/123) +- #121 _Fix the bug_ in #123 - Fixed bug - Improved performance + More nice code @@ -285,7 +285,7 @@ def __init__(self, name): """ RELEASE_NOTES_DATA_CLOSED_ISSUE_WITH_MERGED_PRS_WITH_USER_LABELS = """### Chapter 1 🛠 -- #122 _I1+bug_ in [#123](https://github.com/org/repo/pull/123) +- #122 _I1+bug_ in #123 - Fixed bug - Improved performance From b8a34b1de1e2cab10be8b4d963b955e089fc0f27 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 12:35:27 +0200 Subject: [PATCH 08/19] - Fixed `Release Notes:` pattern for detection rls notes in PR body. Synced values to capital letters. --- README.md | 2 +- release_notes_generator/utils/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 602ad376..958b65c0 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Add the following step to your GitHub workflow (in example are used non-default This feature searches for release notes in the description of GitHub pull requests, making it easier for maintainers to track changes and updates. - **Format:** - The release notes section have to begin with the title `Release Notes:`, followed by the release notes in bullet points. [See Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). - - If no release notes line is detected under the Release notes: "title," no release notes will be printed in the output. + - If no release notes line is detected under the `Release Notes:` title, no release notes will be printed in the output. - **Example:** - Here are examples of how to structure the release notes (case-sensitive): ``` diff --git a/release_notes_generator/utils/constants.py b/release_notes_generator/utils/constants.py index 875e3d7c..5b4b3377 100644 --- a/release_notes_generator/utils/constants.py +++ b/release_notes_generator/utils/constants.py @@ -48,7 +48,7 @@ ISSUE_STATE_ALL = "all" # Release notes comment constants -RELEASE_NOTE_DETECTION_PATTERN = "Release notes:" +RELEASE_NOTE_DETECTION_PATTERN = "Release Notes:" RELEASE_NOTE_LINE_MARKS = ["-", "*", "+"] # Service chapters titles From cf8ed9207d3f138744f954d63cd4a080acff36f3 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 12:39:42 +0200 Subject: [PATCH 09/19] - Rewording. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 958b65c0..8cf106c1 100644 --- a/README.md +++ b/README.md @@ -181,9 +181,10 @@ Release Notes: + This update introduces a new caching mechanism that improves performance by 20%. ``` - - The extraction process supports all three types of bullet points: `-`, `*`, and `+`, and their combinations even when mixed. (It is not recommended to mix them.) +The extraction process supports all three types of bullet points: `-`, `*`, and `+`, and their combinations. (GitHub documentation do not recommend to mix them.) + - **Best Practice:** Select one character from `-`, `*`, `+` for bullet points. The Markdown parser will automatically format them as a list. -- **Optional:** Usage of release notes is not mandatory for this GH action. +- **Optional usage:** The release notes section is not mandatory for GH action to work. #### Handling Multiple PRs If an issue is linked to multiple PRs, the action fetches and aggregates contributions from all linked PRs. From 5eee2e95bf800418adf6070e6d77d1882d4bf103 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 12:43:10 +0200 Subject: [PATCH 10/19] - Add type hint. --- release_notes_generator/model/record.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_notes_generator/model/record.py b/release_notes_generator/model/record.py index ec2d7bdc..e4780aca 100644 --- a/release_notes_generator/model/record.py +++ b/release_notes_generator/model/record.py @@ -167,7 +167,7 @@ def contains_release_notes(self) -> bool: if self.__is_release_note_detected: return self.__is_release_note_detected - rls_notes = self.get_rls_notes() + rls_notes: str = self.get_rls_notes() if any(mark in rls_notes for mark in RELEASE_NOTE_LINE_MARKS): self.__is_release_note_detected = True From 3f02bfd4cb479e9caa1575175414d9af6e17bfa4 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 13:06:19 +0200 Subject: [PATCH 11/19] - Fixed text line about test and coverage in README.md. - Reformat of cov-report output format definition. --- .github/workflows/test.yml | 2 +- README.md | 6 ++++-- main.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c3c6bfa..f68b3d1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,7 +102,7 @@ jobs: run: echo "PYTHONPATH=${GITHUB_WORKSPACE}/release_notes_generator/release_notes_generator" >> $GITHUB_ENV - name: Build and run unit tests - run: pytest --cov=release_notes_generator --cov-report html tests/ -vv + run: pytest --cov=release_notes_generator --cov-report=html tests/ -vv - name: Check overall coverage run: | diff --git a/README.md b/README.md index 8cf106c1..bf5bd74c 100644 --- a/README.md +++ b/README.md @@ -340,16 +340,18 @@ Unit tests are written using pytest. To run the tests, use the following command pytest tests/ ``` -This will execute all tests located in the tests directory and generate a code coverage report. +This will execute all tests located in the tests directory. ## Code Coverage Code coverage is collected using pytest-cov coverage tool. To run the tests and collect coverage information, use the following command: ``` -pytest --cov=release_notes_generator --cov-report html tests/ +pytest --cov=release_notes_generator --cov-report=html tests/ ``` +This will execute all tests located in the tests directory and generate a code coverage report. + See the coverage report on the path: ``` diff --git a/main.py b/main.py index cdfde699..39bf3548 100644 --- a/main.py +++ b/main.py @@ -52,7 +52,7 @@ def run() -> None: generator = ReleaseNotesGenerator(py_github, custom_chapters) rls_notes = generator.generate() - logger.debug("Release notes: \n%s", rls_notes) + logger.debug("Generated release notes: \n%s", rls_notes) # Set the output for the GitHub Action set_action_output("release-notes", rls_notes) From 9e11d4d8d7bc7565f28b8281afac96c2255863c7 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 13:12:24 +0200 Subject: [PATCH 12/19] - Moved mention of case-sensivity for `Release Notes:` detection string. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf5bd74c..d2707ef3 100644 --- a/README.md +++ b/README.md @@ -166,10 +166,10 @@ Add the following step to your GitHub workflow (in example are used non-default #### Release Notes Extraction Process This feature searches for release notes in the description of GitHub pull requests, making it easier for maintainers to track changes and updates. - **Format:** - - The release notes section have to begin with the title `Release Notes:`, followed by the release notes in bullet points. [See Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). + - The release notes section have to begin with the title `Release Notes:` (case-sensitive), followed by the release notes in bullet points. [See Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). - If no release notes line is detected under the `Release Notes:` title, no release notes will be printed in the output. - **Example:** - - Here are examples of how to structure the release notes (case-sensitive): + - Here are examples of how to structure the release notes: ``` Release Notes: - This update introduces a new caching mechanism that improves performance by 20%. From 24886a82fc4a02749ba25d157e3d5a8b56b80357 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 13:35:06 +0200 Subject: [PATCH 13/19] - Small changes and improvement from review notes. --- .github/workflows/check_pr_release_notes.yml | 2 +- release_notes_generator/generator.py | 2 +- release_notes_generator/model/base_chapters.py | 2 +- release_notes_generator/model/service_chapters.py | 11 +++++++++++ release_notes_generator/record/record_factory.py | 9 ++++++++- release_notes_generator/utils/decorators.py | 4 ++-- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml index 88ebf643..11520314 100644 --- a/.github/workflows/check_pr_release_notes.yml +++ b/.github/workflows/check_pr_release_notes.yml @@ -63,7 +63,7 @@ jobs: if: steps.pr_info.outputs.skip_check == 'true' run: echo "Skipping release notes validation." - - name: Check for 'Release notes:' and bullet list + - name: Check for 'Release Notes:' and bullet list if: steps.pr_info.outputs.skip_check == 'false' run: | # Extract the body from the previous step diff --git a/release_notes_generator/generator.py b/release_notes_generator/generator.py index c59abb2a..c15e6e11 100644 --- a/release_notes_generator/generator.py +++ b/release_notes_generator/generator.py @@ -92,7 +92,7 @@ def generate(self) -> Optional[str]: if rls is not None: logger.info("Count of issues: %d", len(list(issues))) - # filter out merged PRs and commits before the since date + # filter out merged PRs and commits before the date pulls = list(filter(lambda pull: pull.merged_at is not None and pull.merged_at > since, list(pulls_all))) logger.debug("Count of pulls reduced from %d to %d", len(list(pulls_all)), len(pulls)) diff --git a/release_notes_generator/model/base_chapters.py b/release_notes_generator/model/base_chapters.py index fd3514a6..7b5bbd9b 100644 --- a/release_notes_generator/model/base_chapters.py +++ b/release_notes_generator/model/base_chapters.py @@ -82,7 +82,7 @@ def titles(self) -> list[str]: return [chapter.title for chapter in self.chapters.values()] @abstractmethod - def populate(self, records: dict[int, Record]): + def populate(self, records: dict[int, Record]) -> None: """ Populates the chapters with records. diff --git a/release_notes_generator/model/service_chapters.py b/release_notes_generator/model/service_chapters.py index 7eaf3d09..c021d6c6 100644 --- a/release_notes_generator/model/service_chapters.py +++ b/release_notes_generator/model/service_chapters.py @@ -205,8 +205,19 @@ def __populate_pr(self, record: Record, nr: int) -> None: self.used_record_numbers.append(nr) def __is_row_present(self, nr: int) -> bool: + """ + Checks if the row is already present in the chapters. + + @param nr: The number of the record. + @return: True if the row is present, False otherwise. + """ return nr in self.used_record_numbers @staticmethod def duplicity_allowed() -> bool: + """ + Checks if duplicity is allowed in the service chapters. + + @return: True if duplicity is allowed, False otherwise. + """ return ActionInputs.get_duplicity_scope() in (DuplicityScopeEnum.SERVICE, DuplicityScopeEnum.BOTH) diff --git a/release_notes_generator/record/record_factory.py b/release_notes_generator/record/record_factory.py index 92e6b0d9..c6f6f4b4 100644 --- a/release_notes_generator/record/record_factory.py +++ b/release_notes_generator/record/record_factory.py @@ -58,7 +58,14 @@ def generate( records = {} pull_numbers = [pull.number for pull in pulls] - def create_record_for_issue(r: Repository, i: Issue): + def create_record_for_issue(r: Repository, i: Issue) -> None: + """ + Create a record for an issue. + + @param r: Repository instance. + @param i: Issue instance. + @return: None + """ records[i.number] = Record(r, i) logger.debug("Created record for issue %d: %s", i.number, i.title) diff --git a/release_notes_generator/utils/decorators.py b/release_notes_generator/utils/decorators.py index 5cd94eea..1448570f 100644 --- a/release_notes_generator/utils/decorators.py +++ b/release_notes_generator/utils/decorators.py @@ -23,7 +23,7 @@ from functools import wraps from typing import Callable, Optional, Any from github import GithubException -from requests.exceptions import Timeout, RequestException, ConnectionError as RequestsConnectionError +from requests.exceptions import Timeout, RequestException from release_notes_generator.utils.github_rate_limiter import GithubRateLimiter logger = logging.getLogger(__name__) @@ -64,7 +64,7 @@ def decorator(method: Callable) -> Callable: def wrapped(*args, **kwargs) -> Optional[Any]: try: return method(*args, **kwargs) - except (RequestsConnectionError, Timeout) as e: + except (ConnectionError, Timeout) as e: logger.error("Network error calling %s: %s", method.__name__, e, exc_info=True) return None except GithubException as e: From 8d46ba8bfca8af446f03bb33918e3c8986fb7099 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 13:50:31 +0200 Subject: [PATCH 14/19] - Fixed unit tests. --- tests/conftest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d65be12a..a475eb8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -174,7 +174,7 @@ def mock_issue_closed_i1_bug(mocker): def mock_pull_closed(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Release notes:\n- Fixed bug\n- Improved performance\n+ More nice code\n * Awesome architecture" + pull.body = "Release Notes:\n- Fixed bug\n- Improved performance\n+ More nice code\n * Awesome architecture" pull.url = "http://example.com/pull/123" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -193,7 +193,7 @@ def mock_pull_closed(mocker): def mock_pull_closed_with_rls_notes_101(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Release notes:\n- PR 101 1st release note\n- PR 101 2nd release note\n" + pull.body = "Release Notes:\n- PR 101 1st release note\n- PR 101 2nd release note\n" pull.url = "http://example.com/pull/101" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -212,7 +212,7 @@ def mock_pull_closed_with_rls_notes_101(mocker): def mock_pull_closed_with_rls_notes_102(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Release notes:\n- PR 102 1st release note\n- PR 102 2nd release note\n" + pull.body = "Release Notes:\n- PR 102 1st release note\n- PR 102 2nd release note\n" pull.url = "http://example.com/pull/102" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -231,7 +231,7 @@ def mock_pull_closed_with_rls_notes_102(mocker): def mock_pull_merged_with_rls_notes_101(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Closes #122\n\nRelease notes:\n- PR 101 1st release note\n- PR 101 2nd release note\n" + pull.body = "Closes #122\n\nRelease Notes:\n- PR 101 1st release note\n- PR 101 2nd release note\n" pull.url = "http://example.com/pull/101" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -250,7 +250,7 @@ def mock_pull_merged_with_rls_notes_101(mocker): def mock_pull_merged_with_rls_notes_102(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Closes #123\n\nRelease notes:\n- PR 102 1st release note\n- PR 102 2nd release note\n" + pull.body = "Closes #123\n\nRelease Notes:\n- PR 102 1st release note\n- PR 102 2nd release note\n" pull.url = "http://example.com/pull/102" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -269,7 +269,7 @@ def mock_pull_merged_with_rls_notes_102(mocker): def mock_pull_merged(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_CLOSED - pull.body = "Release notes:\n- Fixed bug\n- Improved performance\n" + pull.body = "Release Notes:\n- Fixed bug\n- Improved performance\n" pull.url = "http://example.com/pull/123" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -288,7 +288,7 @@ def mock_pull_merged(mocker): def mock_pull_open(mocker): pull = mocker.Mock(spec=PullRequest) pull.state = PR_STATE_OPEN - pull.body = "Release notes:\n- Fixed bug\n- Improved performance\n" + pull.body = "Release Notes:\n- Fixed bug\n- Improved performance\n" pull.url = "http://example.com/pull/123" label1 = mocker.Mock(spec=MockLabel) label1.name = "label1" @@ -450,7 +450,7 @@ def record_with_no_issue_one_pull_merged_with_issue_mentioned(request): record = Record(repo=(mock_repo_fixture := request.getfixturevalue("mock_repo"))) mock_repo_fixture.full_name = "org/repo" mock_pull_merged_fixture = request.getfixturevalue("mock_pull_merged") - mock_pull_merged_fixture.body = "Release notes:\n- Fixed bug\n- Improved performance\n\nFixes #123" + mock_pull_merged_fixture.body = "Release Notes:\n- Fixed bug\n- Improved performance\n\nFixes #123" record.register_pull_request(mock_pull_merged_fixture) record.register_commit(request.getfixturevalue("mock_commit")) return record From b831fb1b83bfd05143fa68ce013c24e97d8a3b68 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Wed, 16 Oct 2024 14:19:23 +0200 Subject: [PATCH 15/19] - Removed TODO from pylintrc. - Removed no more required workflow for copying Release notes comments from PR to Issues. - Update logic of "skip-release-notes-check" label check presence. --- .github/workflows/check_pr_release_notes.yml | 10 +- .../release_notes_comments_migration.yml | 110 ------------------ .pylintrc | 6 +- 3 files changed, 6 insertions(+), 120 deletions(-) delete mode 100644 .github/workflows/release_notes_comments_migration.yml diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml index 11520314..e64b5119 100644 --- a/.github/workflows/check_pr_release_notes.yml +++ b/.github/workflows/check_pr_release_notes.yml @@ -59,7 +59,7 @@ jobs: core.setOutput("skip_check", 'false'); return; - - name: Skip check if 'no-release-notes' label is present + - name: Skip check if 'skip-release-notes-check' label is present if: steps.pr_info.outputs.skip_check == 'true' run: echo "Skipping release notes validation." @@ -70,16 +70,16 @@ jobs: PR_BODY="${{ steps.pr_info.outputs.pr_body }}" # Check if "Release notes:" exists - if ! echo "$PR_BODY" | grep -qi 'release notes:'; then - echo "Error: 'Release notes:' not found in pull request description." + if ! echo "$PR_BODY" | grep -q 'Release Notes:'; then + echo "Error: 'Release Notes:' not found in pull request description." exit 1 fi # Extract text after "Release notes:" line - RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/[Rr]elease.*[Nn]otes/,$p' | tail -n +2) + RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/Release.*Notes/,$p' | tail -n +2) # Check if there's a bullet list (lines starting with '-' or '*') if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-*]\s+.+$'; then - echo "Error: No bullet list found under 'Release notes:'." + echo "Error: No bullet list found under 'Release Notes:'." exit 1 fi diff --git a/.github/workflows/release_notes_comments_migration.yml b/.github/workflows/release_notes_comments_migration.yml deleted file mode 100644 index 61103777..00000000 --- a/.github/workflows/release_notes_comments_migration.yml +++ /dev/null @@ -1,110 +0,0 @@ -# -# Copyright 2023 ABSA Group Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Copy Release Notes to Related Issues - -on: - pull_request: - types: [closed] - branches: [ master ] - -jobs: - copy_release_notes: - if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - steps: - - name: Fetch PR Comments - id: get-comments - uses: actions/github-script@v7 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const prNumber = context.payload.pull_request.number; - const repoName = context.repo.repo; - const repoOwner = context.repo.owner; - const releaseNotesRegex = /release notes/i; - - const comments = await github.rest.issues.listComments({ - owner: repoOwner, - repo: repoName, - issue_number: prNumber, - }); - - const releaseNoteComment = comments.data.find(comment => releaseNotesRegex.test(comment.body)); - const releaseNoteBody = releaseNoteComment ? releaseNoteComment.body : ''; - console.log(`Release Note Body: ${releaseNoteBody}`); - core.setOutput('releaseNoteBody', releaseNoteBody); - - - name: Print Extracted releaseNoteBody - run: | - echo "Extracted Release Note Body:" - echo "${{ steps.get-comments.outputs.releaseNoteBody }}" - echo "RELEASE_NOTE_BODY<> $GITHUB_ENV - echo "${{ steps.get-comments.outputs.releaseNoteBody }}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Parse PR Description for Related Issues - id: find-issues - uses: actions/github-script@v7 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const description = context.payload.pull_request.body; - const issueNumbers = []; - const regexPattern = /([Cc]los(e|es|ed)|[Ff]ix(es|ed)?|[Rr]esolv(e|es|ed))\s*#\s*([0-9]+)/g; - - let match; - while ((match = regexPattern.exec(description)) !== null) { - // This is necessary to avoid infinite loops with zero-width matches - if (match.index === regexPattern.lastIndex) { - regexPattern.lastIndex++; - } - - // The actual issue number is in the last group of the match - const issueNumber = match[match.length - 1]; - if (issueNumber) { - issueNumbers.push(issueNumber); - } - } - - core.setOutput('issueNumbers', issueNumbers.join(', ')); - - - name: Print Extracted Issue Numbers - run: | - echo "Extracted Issue Numbers: ${{ steps.find-issues.outputs.issueNumbers }}" - echo "ISSUE_NUMBERS=${{ steps.find-issues.outputs.issueNumbers }}" >> $GITHUB_ENV - - - name: Post Comment to Issues - if: ${{ steps.get-comments.outputs.releaseNoteBody }} && ${{ steps.find-issues.outputs.issueNumbers }} - uses: actions/github-script@v7 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const issueNumbers = process.env.ISSUE_NUMBERS; - const commentBody = process.env.RELEASE_NOTE_BODY; - const repoName = context.repo.repo; - const repoOwner = context.repo.owner; - - for (const issueNumber of issueNumbers.split(', ')) { - if (issueNumber && commentBody) { - await github.rest.issues.createComment({ - owner: repoOwner, - repo: repoName, - issue_number: issueNumber, - body: commentBody - }); - } - } diff --git a/.pylintrc b/.pylintrc index 1ddb6781..17a93a44 100644 --- a/.pylintrc +++ b/.pylintrc @@ -431,11 +431,7 @@ confidence=HIGH, # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -# TODO: Remove after this PR -disable=C0116, - C0115, - C0114, - raw-checker-failed, +disable=raw-checker-failed, bad-inline-option, locally-disabled, file-ignored, From 1622020e2cc0b20daf5055ef8053bee0a77e5bd6 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Thu, 17 Oct 2024 07:54:37 +0200 Subject: [PATCH 16/19] - Removed not needed word. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2707ef3..4ede155d 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Add the following step to your GitHub workflow (in example are used non-default #### Release Notes Extraction Process This feature searches for release notes in the description of GitHub pull requests, making it easier for maintainers to track changes and updates. - **Format:** - - The release notes section have to begin with the title `Release Notes:` (case-sensitive), followed by the release notes in bullet points. [See Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). + - The release notes section have to begin with the title `Release Notes:` (case-sensitive), followed by the release notes in bullet points. [Markdown formatting is supported](https://www.markdownguide.org/basic-syntax/#unordered-lists). - If no release notes line is detected under the `Release Notes:` title, no release notes will be printed in the output. - **Example:** - Here are examples of how to structure the release notes: From 0362f317fcb3cbd2f380ab9338b63bb0baa8f569 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Thu, 17 Oct 2024 09:27:01 +0200 Subject: [PATCH 17/19] - Fix of example file to fit the latest expected version. - Added check for `+` character as valid detection of Release notes line. --- .github/workflows/check_pr_release_notes.yml | 4 ++-- examples/check_pr_release_notes.yml | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml index e64b5119..c0b76e32 100644 --- a/.github/workflows/check_pr_release_notes.yml +++ b/.github/workflows/check_pr_release_notes.yml @@ -78,8 +78,8 @@ jobs: # Extract text after "Release notes:" line RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/Release.*Notes/,$p' | tail -n +2) - # Check if there's a bullet list (lines starting with '-' or '*') - if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-*]\s+.+$'; then + # Check if there's a bullet list (lines starting with '-', '+' or '*') + if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-+*]\s+.+$'; then echo "Error: No bullet list found under 'Release Notes:'." exit 1 fi diff --git a/examples/check_pr_release_notes.yml b/examples/check_pr_release_notes.yml index 5324ba62..dbe73909 100644 --- a/examples/check_pr_release_notes.yml +++ b/examples/check_pr_release_notes.yml @@ -43,27 +43,27 @@ jobs: core.setOutput("skip_check", 'false'); return; - - name: Skip check if 'no-release-notes' label is present + - name: Skip check if 'skip-release-notes-check' label is present if: steps.pr_info.outputs.skip_check == 'true' run: echo "Skipping release notes validation." - - name: Check for 'Release notes:' and bullet list + - name: Check for 'Release Notes:' and bullet list if: steps.pr_info.outputs.skip_check == 'false' run: | # Extract the body from the previous step PR_BODY="${{ steps.pr_info.outputs.pr_body }}" # Check if "Release notes:" exists - if ! echo "$PR_BODY" | grep -qi 'release notes:'; then - echo "Error: 'Release notes:' not found in pull request description." + if ! echo "$PR_BODY" | grep -q 'Release Notes:'; then + echo "Error: 'Release Notes:' not found in pull request description." exit 1 fi # Extract text after "Release notes:" line - RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/[Rr]elease.*[Nn]otes/,$p' | tail -n +2) + RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/Release.*Notes/,$p' | tail -n +2) - # Check if there's a bullet list (lines starting with '-' or '*') - if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-*]\s+.+$'; then - echo "Error: No bullet list found under 'Release notes:'." + # Check if there's a bullet list (lines starting with '-', '+' or '*') + if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-+*]\s+.+$'; then + echo "Error: No bullet list found under 'Release Notes:'." exit 1 fi From 7eb8d247cadd465ae29585d916c700a208c7d630 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Mon, 21 Oct 2024 11:10:25 +0200 Subject: [PATCH 18/19] - Applied changes from external development to keep them as examples and use in workflows. --- .github/workflows/check_pr_release_notes.yml | 40 ++++++++++---------- .github/workflows/release_draft.yml | 19 ++++++++-- examples/check_pr_release_notes.yml | 35 +++++++++-------- examples/release_draft.yml | 24 +++++++++--- 4 files changed, 75 insertions(+), 43 deletions(-) diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml index c0b76e32..7b91a679 100644 --- a/.github/workflows/check_pr_release_notes.yml +++ b/.github/workflows/check_pr_release_notes.yml @@ -14,20 +14,22 @@ # limitations under the License. # -name: Check Release Notes in PR description +name: Check PR Release Notes in Description on: pull_request: types: [opened, synchronize, reopened, edited, labeled, unlabeled] branches: [ master ] +env: + SKIP_LABEL: 'no RN' + RLS_NOTES_TAG_REGEX: 'Release Notes:' + jobs: check-release-notes: runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 + steps: - name: Get Pull Request Info id: pr_info uses: actions/github-script@v7 @@ -40,11 +42,11 @@ jobs: pull_number: pr_number }); const labels = pr.data.labels ? pr.data.labels.map(label => label.name) : []; - - // Check if "skip-release-notes-check" label is present - if (labels.includes("skip-release-notes-check")) { - console.log("Skipping release notes check because 'skip-release-notes-check' label is present."); + + if (labels.includes("${{ env.SKIP_LABEL }}")) { + console.log("Skipping release notes check because '${{ env.SKIP_LABEL }}' label is present."); core.setOutput("skip_check", 'true'); + core.setOutput("pr_body", ""); return; } @@ -59,7 +61,7 @@ jobs: core.setOutput("skip_check", 'false'); return; - - name: Skip check if 'skip-release-notes-check' label is present + - name: Skip check if SKIP_LABEL is present if: steps.pr_info.outputs.skip_check == 'true' run: echo "Skipping release notes validation." @@ -68,18 +70,18 @@ jobs: run: | # Extract the body from the previous step PR_BODY="${{ steps.pr_info.outputs.pr_body }}" - - # Check if "Release notes:" exists - if ! echo "$PR_BODY" | grep -q 'Release Notes:'; then - echo "Error: 'Release Notes:' not found in pull request description." + + # Check if "Release Notes:" exists + if ! echo "$PR_BODY" | grep -q '${{ env.RLS_NOTES_TAG_REGEX }}'; then + echo "Error: release notes tag not found in pull request description. Has to adhere to format '${{ env.RLS_NOTES_TAG_REGEX }}'." exit 1 fi - - # Extract text after "Release notes:" line - RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/Release.*Notes/,$p' | tail -n +2) - + + # Extract text after "Release Notes:" line + TEXT_BELOW_RELEASE_NOTES_TAG=$(echo "$PR_BODY" | sed -n '/${{ env.RLS_NOTES_TAG_REGEX }}/,$p' | tail -n +2) + # Check if there's a bullet list (lines starting with '-', '+' or '*') - if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-+*]\s+.+$'; then - echo "Error: No bullet list found under 'Release Notes:'." + if ! echo "$TEXT_BELOW_RELEASE_NOTES_TAG" | grep -qE '^\s*[-+*]\s+.+$'; then + echo "Error: No bullet list found under release notes tag." exit 1 fi diff --git a/.github/workflows/release_draft.yml b/.github/workflows/release_draft.yml index 35731fc4..22249417 100644 --- a/.github/workflows/release_draft.yml +++ b/.github/workflows/release_draft.yml @@ -26,7 +26,7 @@ jobs: check-tag: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -78,11 +78,11 @@ jobs: tag-name: ${{ github.event.inputs.tag-name }} - generate-release-notes: + release-draft: needs: check-tag runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -98,13 +98,24 @@ jobs: with: tag-name: ${{ github.event.inputs.tag-name }} chapters: '[ + {"title": "No entry 🚫", "label": "duplicate"}, + {"title": "No entry 🚫", "label": "invalid"}, + {"title": "No entry 🚫", "label": "wontfix"}, + {"title": "No entry 🚫", "label": "no RN"}, {"title": "Breaking Changes 💥", "label": "breaking-change"}, {"title": "New Features 🎉", "label": "enhancement"}, {"title": "New Features 🎉", "label": "feature"}, - {"title": "Bugfixes 🛠", "label": "bug"} + {"title": "Bugfixes 🛠", "label": "bug"}, + {"title": "Infrastructure ⚙️", "label": "infrastructure"}, + {"title": "Silent-live 🤫", "label": "silent-live"}, + {"title": "Documentation 📜", "label": "documentation"} ]' + skip-release-notes-label: 'no RN' verbose: true + warnings: true + print-empty-chapters: true + chapters-to-pr-without-issue: true - name: Create and Push Tag diff --git a/examples/check_pr_release_notes.yml b/examples/check_pr_release_notes.yml index dbe73909..bc1820e5 100644 --- a/examples/check_pr_release_notes.yml +++ b/examples/check_pr_release_notes.yml @@ -5,9 +5,14 @@ on: types: [opened, synchronize, reopened, edited, labeled, unlabeled] branches: [ master ] +env: + SKIP_LABEL: 'no RN' + RLS_NOTES_TAG_REGEX: 'Release Notes:' + jobs: check-release-notes: runs-on: {your-runner} + steps: - name: Checkout code uses: actions/checkout@v4 @@ -24,11 +29,11 @@ jobs: pull_number: pr_number }); const labels = pr.data.labels ? pr.data.labels.map(label => label.name) : []; - - // Check if "skip-release-notes-check" label is present - if (labels.includes("skip-release-notes-check")) { - console.log("Skipping release notes check because 'skip-release-notes-check' label is present."); + + if (labels.includes("${{ env.SKIP_LABEL }}")) { + console.log("Skipping release notes check because '${{ env.SKIP_LABEL }}' label is present."); core.setOutput("skip_check", 'true'); + core.setOutput("pr_body", ""); return; } @@ -43,7 +48,7 @@ jobs: core.setOutput("skip_check", 'false'); return; - - name: Skip check if 'skip-release-notes-check' label is present + - name: Skip check if SKIP_LABEL is present if: steps.pr_info.outputs.skip_check == 'true' run: echo "Skipping release notes validation." @@ -52,18 +57,18 @@ jobs: run: | # Extract the body from the previous step PR_BODY="${{ steps.pr_info.outputs.pr_body }}" - - # Check if "Release notes:" exists - if ! echo "$PR_BODY" | grep -q 'Release Notes:'; then - echo "Error: 'Release Notes:' not found in pull request description." + + # Check if "Release Notes:" exists + if ! echo "$PR_BODY" | grep -q '${{ env.RLS_NOTES_TAG_REGEX }}'; then + echo "Error: release notes tag not found in pull request description. Has to adhere to format '${{ env.RLS_NOTES_TAG_REGEX }}'." exit 1 fi - - # Extract text after "Release notes:" line - RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/Release.*Notes/,$p' | tail -n +2) - + + # Extract text after "Release Notes:" line + TEXT_BELOW_RELEASE_NOTES_TAG=$(echo "$PR_BODY" | sed -n '/${{ env.RLS_NOTES_TAG_REGEX }}/,$p' | tail -n +2) + # Check if there's a bullet list (lines starting with '-', '+' or '*') - if ! echo "$RELEASE_NOTES" | grep -qE '^\s*[-+*]\s+.+$'; then - echo "Error: No bullet list found under 'Release Notes:'." + if ! echo "$TEXT_BELOW_RELEASE_NOTES_TAG" | grep -qE '^\s*[-+*]\s+.+$'; then + echo "Error: No bullet list found under release notes tag." exit 1 fi diff --git a/examples/release_draft.yml b/examples/release_draft.yml index 3d2a50ba..a0eb8a5a 100644 --- a/examples/release_draft.yml +++ b/examples/release_draft.yml @@ -9,8 +9,9 @@ on: jobs: check-tag: runs-on: {your-runner} + steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -62,11 +63,12 @@ jobs: tag-name: ${{ github.event.inputs.tag-name }} - generate-release-notes: + release-draft: needs: check-tag runs-on: {your-runner} + steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -82,14 +84,26 @@ jobs: with: tag-name: ${{ github.event.inputs.tag-name }} chapters: '[ + {"title": "No entry 🚫", "label": "duplicate"}, + {"title": "No entry 🚫", "label": "invalid"}, + {"title": "No entry 🚫", "label": "wontfix"}, + {"title": "No entry 🚫", "label": "no RN"}, {"title": "Breaking Changes 💥", "label": "breaking-change"}, {"title": "New Features 🎉", "label": "enhancement"}, {"title": "New Features 🎉", "label": "feature"}, - {"title": "Bugfixes 🛠", "label": "bug"} + {"title": "Bugfixes 🛠", "label": "bug"}, + {"title": "Infrastructure ⚙️", "label": "infrastructure"}, + {"title": "Silent-live 🤫", "label": "silent-live"}, + {"title": "Documentation 📜", "label": "documentation"} ]' - skip-release-notes-label: 'ignore-in-release' # changing default value of label + skip-release-notes-label: 'no RN' verbose: true + warnings: true + print-empty-chapters: true + chapters-to-pr-without-issue: true + + - name: Create and Push Tag uses: actions/github-script@v7 with: From 8af61cdbe5f9245364b8b55910f50b7b841ed3d2 Mon Sep 17 00:00:00 2001 From: miroslavpojer Date: Mon, 21 Oct 2024 11:22:32 +0200 Subject: [PATCH 19/19] - Replace code root dir by . to add all python file. --- .github/workflows/test.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f68b3d1b..710a967f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,7 +102,7 @@ jobs: run: echo "PYTHONPATH=${GITHUB_WORKSPACE}/release_notes_generator/release_notes_generator" >> $GITHUB_ENV - name: Build and run unit tests - run: pytest --cov=release_notes_generator --cov-report=html tests/ -vv + run: pytest --cov=. --cov-report=html tests/ -vv - name: Check overall coverage run: | diff --git a/README.md b/README.md index 4ede155d..60622e5e 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ This will execute all tests located in the tests directory. Code coverage is collected using pytest-cov coverage tool. To run the tests and collect coverage information, use the following command: ``` -pytest --cov=release_notes_generator --cov-report=html tests/ +pytest --cov=. --cov-report=html tests/ ``` This will execute all tests located in the tests directory and generate a code coverage report.