Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- [Motivation](#motivation)
- [Requirements](#requirements)
- [Inputs](#inputs)
- [Feature controls](#feature-controls)
- [Regimes](#regimes)
- [Outputs](#outputs)
- [Usage Example](#usage-example)
- [Features](#features)
Expand Down Expand Up @@ -40,24 +42,25 @@ Generate Release Notes action is dedicated to enhance the quality and organizati

## Inputs

| Name | Description | Required | Default |
|----------------|-----------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------|
| `GITHUB_TOKEN` | Your GitHub token for authentication. Store it as a secret and reference it in the workflow file as secrets.GITHUB_TOKEN. | Yes | |
| `tag-name` | The name of the tag for which you want to generate release notes. This should be the same as the tag name used in the release workflow. | Yes | |
| `from-tag-name` | The name of the tag from which you want to generate release notes. | No | '' |
| `chapters` | An YAML array defining chapters and corresponding labels for categorization. Each chapter should have a title and a label matching your GitHub issues and PRs. | Yes | |
| `row-format-issue` | 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. | No | `"{number} _{title}_ in {pull-requests}"` |
| `row-format-pr` | 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. | No | `"{number} _{title}_"` |
| `row-format-link-pr` | If defined `true`, the PR row will begin with a `"PR: "` string. Otherwise, no prefix will be added. | No | true |
| `duplicity-scope` | Set to `custom` to allow duplicity issue lines to be shown only in custom chapters. Options: `custom`, `service`, `both`, `none`. | No | `both` |
| `duplicity-icon` | The icon used to indicate duplicity issue lines in the release notes. Icon will be placed at the beginning of the line. | No | `🔔` |
| `published-at` | Set to true to enable the use of the `published-at` timestamp as the reference point for searching closed issues and PRs, instead of the `created-at` date of the latest release. If first release, repository creation date is used. | No | false |
| `skip-release-notes-labels` | List labels used for detection if issues or pull requests are ignored in the Release Notes generation process. Example: `skip-release-notes, question`. | No | `skip-release-notes` |
| `verbose` | Set to true to enable verbose logging for detailed output during the action's execution. | No | false |
| `release-notes-title` | The title of the release notes section in the PR description. | No | `[Rr]elease [Nn]otes:` |
| `coderabbit-support-active` | Enable CodeRabbit support. If true, the action will use CodeRabbit to generate release notes. | No | false |
| `coderabbit-release-notes-title` | The title of the CodeRabbit summary in the PR body. Value supports regex. | No | `Summary by CodeRabbit` |
| `coderabbit-summary-ignore-groups` | List of "group names" to be ignored by release notes detection logic. Example: `Documentation, Tests, Chores, Bug Fixes`. | No | '' |
| Name | Description | Required | Default |
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------------------|
| `GITHUB_TOKEN` | Your GitHub token for authentication. Store it as a secret and reference it in the workflow file as secrets.GITHUB_TOKEN. | Yes | |
| `tag-name` | The name of the tag for which you want to generate release notes. This should be the same as the tag name used in the release workflow. | Yes | |
| `from-tag-name` | The name of the tag from which you want to generate release notes. | No | '' |
| `chapters` | An YAML array defining chapters and corresponding labels for categorization. Each chapter should have a title and a label matching your GitHub issues and PRs. | Yes | |
| `regime` | Controls the regime of the action. Options: `default`. See more about the [Regimes](#regimes). | No | `default` |
| `row-format-issue` | 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. | No | `"{number} _{title}_ in {pull-requests}"` |
| `row-format-pr` | 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. | No | `"{number} _{title}_"` |
| `row-format-link-pr` | If defined `true`, the PR row will begin with a `"PR: "` string. Otherwise, no prefix will be added. | No | true |
| `duplicity-scope` | Set to `custom` to allow duplicity issue lines to be shown only in custom chapters. Options: `custom`, `service`, `both`, `none`. | No | `both` |
| `duplicity-icon` | The icon used to indicate duplicity issue lines in the release notes. Icon will be placed at the beginning of the line. | No | `🔔` |
| `published-at` | Set to true to enable the use of the `published-at` timestamp as the reference point for searching closed issues and PRs, instead of the `created-at` date of the latest release. If first release, repository creation date is used. | No | false |
| `skip-release-notes-labels` | List labels used for detection if issues or pull requests are ignored in the Release Notes generation process. Example: `skip-release-notes, question`. | No | `skip-release-notes` |
| `verbose` | Set to true to enable verbose logging for detailed output during the action's execution. | No | false |
| `release-notes-title` | The title of the release notes section in the PR description. | No | `[Rr]elease [Nn]otes:` |
| `coderabbit-support-active` | Enable CodeRabbit support. If true, the action will use CodeRabbit to generate release notes. | No | false |
| `coderabbit-release-notes-title` | The title of the CodeRabbit summary in the PR body. Value supports regex. | No | `Summary by CodeRabbit` |
| `coderabbit-summary-ignore-groups` | List of "group names" to be ignored by release notes detection logic. Example: `Documentation, Tests, Chores, Bug Fixes`. | No | '' |

Comment on lines +45 to 64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Docs/action mismatch: chapters “Required” flag conflicts with action.yml and runtime validation.

  • README marks chapters as Required: Yes.
  • action.yml sets required: false with default ''.
  • validate_inputs() errors when chapters are empty, effectively making them required.

This will confuse users and can fail runs unexpectedly. Align both sources (prefer marking chapters required in action.yml, or relax validation).

To confirm all references, search for where chapters are validated and how the action input is declared:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'get_chapters\(|Chapters must be a valid yaml array' && rg -nP -C2 '^  chapters:\n(\s+.*\n){1,6}\s+required:\s+\w+' action.yml

Length of output: 2907


Require chapters input consistently
chapters is marked required in README but defined as optional (required: false, default '') in action.yml and then enforced as required at runtime in release_notes_generator/action_inputs.py. Update action.yml to required: true (or adjust validation/docs) to eliminate this mismatch.

🤖 Prompt for AI Agents
README.md around lines 45 to 64: the README lists `chapters` as required but
action.yml currently marks it optional and runtime code enforces it required,
causing a mismatch; fix by updating action.yml to set `chapters` required: true
(and remove or adjust the empty default) so metadata matches docs and runtime,
or alternatively update README and runtime validation to mark `chapters`
optional and provide a clear default — ensure the chosen approach is applied
consistently in action.yml, README.md, and
release_notes_generator/action_inputs.py.

> **Notes**
> - `skip-release-notes-labels`
Expand All @@ -78,6 +81,20 @@ Generate Release Notes action is dedicated to enhance the quality and organizati
> - `warnings`
> - **Disabling this feature will hide service chapter showing direct commits!** These cannot be visible in custom chapters as they do not have labels!

### Regimes

### Default regime

The basic regime for this action.

- **Data management**
- The issue type is not used. It can lead to placing Epic and other issues without linked PR into service chapters. If you need to use issue type use another regime.
- **Release notes**
- Organized by custom chapters defined by user using labels.
- Used these `types of rows`:
- Issue with/without linked Pull Request(s).
- Pull Request without linked Issue.
- Direct commits (without Issue and Pull Request).

## Outputs
The output of the action is a markdown string containing the release notes for the specified tag. This string can be used in subsequent steps to publish the release notes to a file, create a GitHub release, or send notifications.
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +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.'
required: false
default: 'default'
duplicity-icon:
description: 'Icon to be used for duplicity warning. Icon is placed before the record line.'
required: false
Expand Down Expand Up @@ -133,6 +137,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_DUPLICITY_SCOPE: ${{ inputs.duplicity-scope }}
INPUT_DUPLICITY_ICON: ${{ inputs.duplicity-icon }}
INPUT_WARNINGS: ${{ inputs.warnings }}
Expand Down
15 changes: 15 additions & 0 deletions release_notes_generator/action_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class ActionInputs:
A class representing the inputs provided to the GH action.
"""

REGIME_DEFAULT = "default"

_row_format_issue = None
_row_format_pr = None
_row_format_link_pr = None
Expand Down Expand Up @@ -160,6 +162,13 @@ def get_chapters() -> list[dict[str, str]]:

return chapters

@staticmethod
def get_regime() -> str:
"""
Get the regime parameter value from the action inputs.
"""
return get_action_input("regime", "default") # type: ignore[return-value] # default defined

@staticmethod
def get_duplicity_scope() -> DuplicityScopeEnum:
"""
Expand Down Expand Up @@ -365,6 +374,11 @@ 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.")

warnings = ActionInputs.get_warnings()
ActionInputs.validate_input(warnings, bool, "Warnings must be a boolean.", errors)

Expand Down Expand Up @@ -421,6 +435,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("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 Down
Empty file.
53 changes: 53 additions & 0 deletions release_notes_generator/builder/base_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#
# 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.
#

"""
This module contains the ReleaseNotesBuilder class which is responsible for building of the release notes.
"""
from abc import ABCMeta, abstractmethod

from release_notes_generator.action_inputs import ActionInputs
from release_notes_generator.model.custom_chapters import CustomChapters
from release_notes_generator.model.record import Record


class ReleaseNotesBuilder(metaclass=ABCMeta):
"""
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()

@abstractmethod
def build(self) -> str:
"""
Build the release notes based on the records, changelog URL, formatter, and custom chapters.

@return: The release notes as a string.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,19 @@
import logging
from itertools import chain

from release_notes_generator.model.custom_chapters import CustomChapters
from release_notes_generator.model.record import Record
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

logger = logging.getLogger(__name__)


class ReleaseNotesBuilder:
class DefaultReleaseNotesBuilder(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 Down
27 changes: 20 additions & 7 deletions release_notes_generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
from release_notes_generator.filter import FilterByRelease
from release_notes_generator.miner import DataMiner
from release_notes_generator.action_inputs import ActionInputs
from release_notes_generator.builder import ReleaseNotesBuilder
from release_notes_generator.builder.base_builder import ReleaseNotesBuilder
from release_notes_generator.builder.default_builder import DefaultReleaseNotesBuilder
from release_notes_generator.model.custom_chapters import CustomChapters
from release_notes_generator.model.record import Record
from release_notes_generator.record.default_record_factory import DefaultRecordFactory
from release_notes_generator.record.record_factory import RecordFactory
from release_notes_generator.utils.github_rate_limiter import GithubRateLimiter
from release_notes_generator.utils.utils import get_change_url
Expand Down Expand Up @@ -90,14 +92,25 @@ def generate(self) -> Optional[str]:

assert data_filtered_by_release.repository is not None, "Repository must not be None"

rls_notes_records: dict[int | str, Record] = RecordFactory.generate(
# get record factory instance in dependency on selected regime
record_factory: RecordFactory = DefaultRecordFactory()
# This is a placeholder for future regimes - will be added in following issue
# match ActionInputs.get_regime():
# case "TODO":
# record_factory = TBD

rls_notes_records: dict[int | str, Record] = record_factory.generate(
github=self._github_instance, data=data_filtered_by_release
)

release_notes_builder = ReleaseNotesBuilder(
records=rls_notes_records,
custom_chapters=self.custom_chapters,
return self._get_rls_notes_builder(rls_notes_records, changelog_url, self.custom_chapters).build()

def _get_rls_notes_builder(
self, records: dict[int | str, Record], changelog_url: str, custom_chapters: CustomChapters
) -> ReleaseNotesBuilder:

return DefaultReleaseNotesBuilder(
records=records,
custom_chapters=custom_chapters,
changelog_url=changelog_url,
)

return release_notes_builder.build()
Loading
Loading