Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
361a818
#167 - Introduce regime for hierarchical release note
miroslavpojer Sep 9, 2025
5da964d
- Interim backup of state: unit test "test_build_hierarchy_issue_with…
miroslavpojer Sep 11, 2025
893517b
- Fixes of logical issues in code. First unit test is now working.
miroslavpojer Sep 12, 2025
64717e2
- Fixes all mypy alerts and applied black.
miroslavpojer Sep 12, 2025
ff66d7f
- Partial fix of pylint.
miroslavpojer Sep 12, 2025
953a926
- Fixed all historical unit tests.
miroslavpojer Sep 12, 2025
adc7c5d
- Solved two TODOs.
miroslavpojer Sep 12, 2025
01c5fd9
- Fixed issue found during real runtime.
miroslavpojer Sep 12, 2025
69e799a
- Fixed form real repo run.
miroslavpojer Sep 12, 2025
9d793df
- Implemented Hierarchy in rls notes output. Service chapters will be…
miroslavpojer Sep 15, 2025
a91423b
- Remove show-only files.
miroslavpojer Sep 15, 2025
de47c3d
- Applied changes:
miroslavpojer Sep 15, 2025
62a5e0e
- Fixed origin unit tests.
miroslavpojer Sep 15, 2025
8c2976d
- Partial fix of mypy alerts.
miroslavpojer Sep 15, 2025
c38193d
- Fixed all check alerts on local.
miroslavpojer Sep 17, 2025
94f1ce2
- Relocated test files to follow latest source code structure.
miroslavpojer Sep 17, 2025
6808247
- Finished 1st unit test for hierarchy issue factory - without labels…
miroslavpojer Sep 18, 2025
2ce5466
- Expanded 1st unit test for hierarchy issue factory.
miroslavpojer Sep 22, 2025
85c743f
- Fix of failing unit tests influenced latest code changes.
miroslavpojer Sep 22, 2025
1a5d9e8
- Added new unit test for final coverage of logic.
miroslavpojer Sep 22, 2025
f0bd99a
Fixed problem from checkers and linters.
miroslavpojer Sep 22, 2025
2235fba
Fixed Rabbit review notes - outside diff range.
miroslavpojer Sep 24, 2025
c034c75
Rabbit: Fixed review notes and several nitpicks.
miroslavpojer Sep 24, 2025
eb5cdbc
Rabbit: Fixed several nitpicks.
miroslavpojer Sep 24, 2025
4248b9a
Rabbit: Fixed several nitpicks.
miroslavpojer Sep 24, 2025
51d2c15
Rabbit: Fixed several nitpicks.
miroslavpojer Sep 24, 2025
bdd10ed
Fixed pytest warning.
miroslavpojer Sep 24, 2025
720ef35
Fixed Rabbit review notes.
miroslavpojer Sep 24, 2025
3923e17
Fixed Rabbit review notes.
miroslavpojer Sep 24, 2025
91fdaa1
Fix linters.
miroslavpojer Sep 24, 2025
7cc7b71
Fix Rabbit review notes.
miroslavpojer Sep 24, 2025
1d294f3
One change reverted to fix issue.
miroslavpojer Sep 24, 2025
294ed5e
Fix Rabbit review notes.
miroslavpojer Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,10 @@ max-attributes=7
max-bool-expr=5

# Maximum number of branch for function / method body.
max-branches=12
max-branches=13

# Maximum number of locals for function / method body.
max-locals=15
max-locals=20

# Maximum number of parents for a class (see R0901).
max-parents=7
Expand All @@ -316,7 +316,7 @@ max-parents=7
max-public-methods=20

# Maximum number of return / yield for function / method body.
max-returns=6
max-returns=7

# Maximum number of statements in function / method body.
max-statements=50
Expand Down
66 changes: 26 additions & 40 deletions README.md

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.
#

name: 'Release notes scrapper'
name: 'Release notes scraper'
description: 'Automatically generates release notes for a given release tag, categorized into chapters based on labels.'
inputs:
tag-name:
Expand All @@ -32,10 +32,10 @@ inputs:
description: 'The tag name of the previous release to use as a start reference point for the current release notes.'
required: false
default: ''
regime:
description: 'Regime of the release notes generation. Options: default.'
hierarchy:
description: 'Use hierarchy of issues and pull requests.'
required: false
default: 'default'
default: 'false'
duplicity-icon:
description: 'Icon to be used for duplicity warning. Icon is placed before the record line.'
required: false
Expand Down Expand Up @@ -76,12 +76,16 @@ inputs:
description: 'List of "group names" to be ignored by release notes detection logic.'
required: false
default: ''
row-format-hierarchy-issue:
description: 'Format of the hierarchy issue in the release notes. Available placeholders: {number}, {title}, {type}. Placeholders are case-insensitive.'
required: false
default: '{type}: _{title}_ {number}'
row-format-issue:
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}'
row-format-pr:
description: 'Format of the pr row in the release notes. Available placeholders: {number}, {title}, {pull-requests}. Placeholders are case-insensitive.'
description: 'Format of the pr row in the release notes. Available placeholders: {number}, {title}. Placeholders are case-insensitive.'
required: false
default: '{number} _{title}_'
row-format-link-pr:
Expand Down Expand Up @@ -124,10 +128,10 @@ runs:
pip install -r ${{ github.action_path }}/requirements.txt
shell: bash

- name: Set PROJECT_ROOT and update PYTHONPATH
- name: Update PYTHONPATH
run: |
ACTION_ROOT="${{ github.action_path }}"
export PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/release_notes_generator"
echo PYTHONPATH="${PYTHONPATH}:${ACTION_ROOT}/release_notes_generator" >> $GITHUB_ENV
shell: bash

- name: Call Release Notes Generator
Expand All @@ -137,7 +141,7 @@ runs:
INPUT_TAG_NAME: ${{ inputs.tag-name }}
INPUT_CHAPTERS: ${{ inputs.chapters }}
INPUT_FROM_TAG_NAME: ${{ inputs.from-tag-name }}
INPUT_REGIME: ${{ inputs.regime }}
INPUT_HIERARCHY: ${{ inputs.hierarchy }}
INPUT_DUPLICITY_SCOPE: ${{ inputs.duplicity-scope }}
INPUT_DUPLICITY_ICON: ${{ inputs.duplicity-icon }}
INPUT_WARNINGS: ${{ inputs.warnings }}
Expand All @@ -150,6 +154,7 @@ runs:
INPUT_CODERABBIT_RELEASE_NOTES_TITLE: ${{ inputs.coderabbit-release-notes-title }}
INPUT_CODERABBIT_SUMMARY_IGNORE_GROUPS: ${{ inputs.coderabbit-summary-ignore-groups }}
INPUT_GITHUB_REPOSITORY: ${{ github.repository }}
INPUT_ROW_FORMAT_HIERARCHY_ISSUE: ${{ inputs.row-format-hierarchy-issue }}
INPUT_ROW_FORMAT_ISSUE: ${{ inputs.row-format-issue }}
INPUT_ROW_FORMAT_PR: ${{ inputs.row-format-pr }}
INPUT_ROW_FORMAT_LINK_PR: ${{ inputs.row-format-link-pr }}
Expand Down
4 changes: 2 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from urllib3.exceptions import InsecureRequestWarning

from release_notes_generator.generator import ReleaseNotesGenerator
from release_notes_generator.model.custom_chapters import CustomChapters
from release_notes_generator.chapters.custom_chapters import CustomChapters
from release_notes_generator.action_inputs import ActionInputs
from release_notes_generator.utils.gh_action import set_action_output
from release_notes_generator.utils.logging_config import setup_logging
Expand All @@ -49,7 +49,7 @@ def run() -> None:
py_github = Github(auth=Auth.Token(token=ActionInputs.get_github_token()), per_page=100, verify=False, timeout=60)

ActionInputs.validate_inputs()
# Load custom chapters configuration

custom_chapters = CustomChapters(print_empty_chapters=ActionInputs.get_print_empty_chapters()).from_yaml_array(
ActionInputs.get_chapters()
)
Expand Down
66 changes: 54 additions & 12 deletions release_notes_generator/action_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@
SKIP_RELEASE_NOTES_LABELS,
RELEASE_NOTES_TITLE,
RELEASE_NOTE_TITLE_DEFAULT,
SUPPORTED_ROW_FORMAT_KEYS,
FROM_TAG_NAME,
CODERABBIT_SUPPORT_ACTIVE,
CODERABBIT_RELEASE_NOTES_TITLE,
CODERABBIT_RELEASE_NOTE_TITLE_DEFAULT,
CODERABBIT_SUMMARY_IGNORE_GROUPS,
ROW_FORMAT_HIERARCHY_ISSUE,
SUPPORTED_ROW_FORMAT_KEYS_ISSUE,
SUPPORTED_ROW_FORMAT_KEYS_PULL_REQUEST,
SUPPORTED_ROW_FORMAT_KEYS_HIERARCHY_ISSUE,
)
from release_notes_generator.utils.enums import DuplicityScopeEnum
from release_notes_generator.utils.gh_action import get_action_input
Expand All @@ -63,8 +66,11 @@ class ActionInputs:
A class representing the inputs provided to the GH action.
"""

REGIME_DEFAULT = "default"
ROW_TYPE_ISSUE = "Issue"
ROW_TYPE_PR = "PR"
ROW_TYPE_HIERARCHY_ISSUE = "HierarchyIssue"

_row_format_hierarchy_issue = None
_row_format_issue = None
_row_format_pr = None
_row_format_link_pr = None
Expand Down Expand Up @@ -163,11 +169,12 @@ def get_chapters() -> list[dict[str, str]]:
return chapters

@staticmethod
def get_regime() -> str:
def get_hierarchy() -> bool:
"""
Get the regime parameter value from the action inputs.
Check if the hierarchy release notes structure is enabled.
"""
return get_action_input("regime", "default") # type: ignore[return-value] # default defined
val = get_action_input("hierarchy", "false")
return str(val).strip().lower() in ("true", "1", "yes", "y", "on")

@staticmethod
def get_duplicity_scope() -> DuplicityScopeEnum:
Expand Down Expand Up @@ -296,6 +303,21 @@ def validate_input(input_value, expected_type: type, error_message: str, error_b
return False
return True

@staticmethod
def get_row_format_hierarchy_issue() -> str:
"""
Get the hierarchy issue row format for the release notes.
"""
if ActionInputs._row_format_hierarchy_issue is None:
ActionInputs._row_format_hierarchy_issue = ActionInputs._detect_row_format_invalid_keywords(
get_action_input(
ROW_FORMAT_HIERARCHY_ISSUE, "{type}: _{title}_ {number}"
).strip(), # type: ignore[union-attr]
row_type=ActionInputs.ROW_TYPE_HIERARCHY_ISSUE,
clean=True,
)
return ActionInputs._row_format_hierarchy_issue

@staticmethod
def get_row_format_issue() -> str:
"""
Expand All @@ -319,6 +341,7 @@ def get_row_format_pr() -> str:
if ActionInputs._row_format_pr is None:
ActionInputs._row_format_pr = ActionInputs._detect_row_format_invalid_keywords(
get_action_input(ROW_FORMAT_PR, "{number} _{title}_").strip(), # type: ignore[union-attr]
row_type=ActionInputs.ROW_TYPE_PR,
clean=True,
# mypy: string is returned as default
)
Expand Down Expand Up @@ -374,10 +397,8 @@ def validate_inputs() -> None:
if not isinstance(duplicity_icon, str) or not duplicity_icon.strip() or len(duplicity_icon) != 1:
errors.append("Duplicity icon must be a non-empty string and have a length of 1.")

regime = ActionInputs.get_regime()
ActionInputs.validate_input(regime, str, "Regime must be a string.", errors)
if regime not in [ActionInputs.REGIME_DEFAULT]:
errors.append(f"Regime '{regime}' is not supported.")
hierarchy: bool = ActionInputs.get_hierarchy()
ActionInputs.validate_input(hierarchy, bool, "Hierarchy must be a boolean.", errors)

warnings = ActionInputs.get_warnings()
ActionInputs.validate_input(warnings, bool, "Warnings must be a boolean.", errors)
Expand Down Expand Up @@ -416,7 +437,14 @@ def validate_inputs() -> None:
if not isinstance(row_format_pr, str) or not row_format_pr.strip():
errors.append("PR Row format must be a non-empty string.")

ActionInputs._detect_row_format_invalid_keywords(row_format_pr, row_type="PR")
ActionInputs._detect_row_format_invalid_keywords(row_format_pr, row_type=ActionInputs.ROW_TYPE_PR)

row_format_hier_issue = ActionInputs.get_row_format_hierarchy_issue()
if not isinstance(row_format_hier_issue, str) or not row_format_hier_issue.strip():
errors.append("Hierarchy Issue row format must be a non-empty string.")
ActionInputs._detect_row_format_invalid_keywords(
row_format_hier_issue, row_type=ActionInputs.ROW_TYPE_HIERARCHY_ISSUE
)

row_format_link_pr = ActionInputs.get_row_format_link_pr()
ActionInputs.validate_input(row_format_link_pr, bool, "'row-format-link-pr' value must be a boolean.", errors)
Expand All @@ -435,7 +463,7 @@ def validate_inputs() -> None:
logger.debug("Tag name: %s", tag_name)
logger.debug("From tag name: %s", from_tag_name)
logger.debug("Chapters: %s", chapters)
logger.debug("Regime: %s", regime)
logger.debug("Hierarchy: %s", hierarchy)
logger.debug("Published at: %s", published_at)
logger.debug("Skip release notes labels: %s", ActionInputs.get_skip_release_notes_labels())
logger.debug("Verbose logging: %s", verbose)
Expand All @@ -456,7 +484,21 @@ def _detect_row_format_invalid_keywords(row_format: str, row_type: str = "Issue"
@return: If clean is True, the cleaned row format. Otherwise, the original row format.
"""
keywords_in_braces = re.findall(r"\{(.*?)\}", row_format)
invalid_keywords = [keyword for keyword in keywords_in_braces if keyword not in SUPPORTED_ROW_FORMAT_KEYS]

mapping = {
ActionInputs.ROW_TYPE_ISSUE: SUPPORTED_ROW_FORMAT_KEYS_ISSUE,
ActionInputs.ROW_TYPE_PR: SUPPORTED_ROW_FORMAT_KEYS_PULL_REQUEST,
ActionInputs.ROW_TYPE_HIERARCHY_ISSUE: SUPPORTED_ROW_FORMAT_KEYS_HIERARCHY_ISSUE,
}
supported_row_format_keys = mapping.get(row_type)
if supported_row_format_keys is None:
logger.warning(
"Unknown row_type '%s' in _detect_row_format_invalid_keywords; defaulting to Issue keys.",
row_type,
)
supported_row_format_keys = SUPPORTED_ROW_FORMAT_KEYS_ISSUE

invalid_keywords = [keyword for keyword in keywords_in_braces if keyword not in supported_row_format_keys]
cleaned_row_format = row_format
for invalid_keyword in invalid_keywords:
logger.error(
Expand Down
53 changes: 0 additions & 53 deletions release_notes_generator/builder/base_builder.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,38 @@
"""
This module contains the ReleaseNotesBuilder class which is responsible for building of the release notes.
"""

import logging

from itertools import chain

from release_notes_generator.builder.base_builder import ReleaseNotesBuilder
from release_notes_generator.model.service_chapters import ServiceChapters
from release_notes_generator.action_inputs import ActionInputs
from release_notes_generator.chapters.custom_chapters import CustomChapters
from release_notes_generator.model.record import Record
from release_notes_generator.chapters.service_chapters import ServiceChapters

logger = logging.getLogger(__name__)


class DefaultReleaseNotesBuilder(ReleaseNotesBuilder):
class ReleaseNotesBuilder:
"""
A class representing the Release Notes Builder.
The class is responsible for building the release notes based on the records, changelog URL, formatter, and custom
chapters.
"""

def __init__(
self,
records: dict[int | str, Record],
changelog_url: str,
custom_chapters: CustomChapters,
):
self.records = records
self.changelog_url = changelog_url
self.custom_chapters = custom_chapters

self.warnings = ActionInputs.get_warnings()
self.print_empty_chapters = ActionInputs.get_print_empty_chapters()

def build(self) -> str:
"""
Build the release notes based on the records, changelog URL, formatter, and custom chapters.
Expand All @@ -53,7 +68,7 @@ def build(self) -> str:
service_chapters = ServiceChapters(
print_empty_chapters=self.print_empty_chapters,
user_defined_labels=user_defined_labels,
used_record_numbers=self.custom_chapters.populated_record_numbers,
used_record_numbers=self.custom_chapters.populated_record_numbers_list,
)
service_chapters.populate(self.records)

Expand Down
Empty file.
Loading
Loading