Skip to content

Add Service chapter visibility control #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
38 changes: 21 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ Generate Release Notes action is dedicated to enhance the quality and organizati
- **Description**: A JSON string defining chapters and corresponding labels for categorization. Each chapter should have a title and a label matching your GitHub issues and PRs.
- **Required**: Yes

### `warnings`
- **Description**: Set to true to enable warnings in the release notes. These warnings identify issues without release notes, without user-defined labels, or without associated pull requests, and PRs without linked issues.
- **Required**: No
- **Default**: false

### `published-at`
- **Description**: 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.
- **Required**: No
Expand All @@ -58,21 +53,29 @@ Generate Release Notes action is dedicated to enhance the quality and organizati
- **Required**: No
- **Default**: `skip-release-notes`

### `print-empty-chapters`
- **Description**: Set to true to print chapters with no issues or PRs.
### `verbose`
- **Description**: Set to true to enable verbose logging for detailed output during the action's execution.
- **Required**: No
- **Default**: false
- **Note**: If workflow run in debug regime, 'verbose' logging is activated.

### `chapters-to-pr-without-issue`
- **Description**: Set to false to avoid application of custom chapters for PRs without linked issues.
### Feature controls

### `warnings`
- **Description**: Set to true to print service chapters in the release notes. These warnings identify issues without release notes, without user-defined labels, or without associated pull requests, and PRs without linked issues.
- **Required**: No
- **Default**: true
- **Default**: true (Service chapters are printed.)

### `verbose`
- **Description**: Set to true to enable verbose logging for detailed output during the action's execution.
### `print-empty-chapters`
- **Description**: Set it to true to print chapters with no issues or PRs.
- **Required**: No
- **Default**: false
- **Note**: If workflow run in debug regime, 'verbose' logging is activated.
- **Default**: true (Empty chapters are printed.)

### `chapters-to-pr-without-issue`
- **Description**: Set it to false to avoid the application of custom chapters for PRs without linked issues.
- **Required**: No
- **Default**: true (Custom chapters are applied to PRs without linked issues.)


## 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 Expand Up @@ -120,12 +123,13 @@ Add the following step to your GitHub workflow (in example are used non-default
{"title": "New Features 🎉", "label": "feature"},
{"title": "Bugfixes 🛠", "label": "bug"}
]'
warnings: false
published-at: true
skip-release-notes-label: 'ignore-in-release' # changing default value of label
verbose: false

warnings: false
print-empty-chapters: false
chapters-to-pr-without-issue: false
verbose: false
```

## Features
Expand Down Expand Up @@ -261,7 +265,7 @@ export GITHUB_REPOSITORY="< owner >/< repo-name >"
export INPUT_GITHUB_TOKEN=$(printenv <your-env-token-var>)

# Run the Python script
python3 /<path-to-action-project-root>/main.py
python3 ./<path-to-action-project-root>/main.py
```

### Contribution Guidelines
Expand Down
8 changes: 4 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ inputs:
chapters:
description: 'JSON string defining chapters and corresponding labels for categorization.'
required: false
warnings:
description: 'Print warning chapters if true.'
required: false
default: 'true'
published-at:
description: 'Use `published-at` timestamp instead of `created-at` as the reference point.'
required: false
Expand All @@ -35,6 +31,10 @@ inputs:
description: 'Skip label used for detection of issues or pull requests to ignore in Release Notes generation process.'
required: false
default: 'skip-release-notes'
warnings:
description: 'Print service chapters if true.'
required: false
default: 'true'
print-empty-chapters:
description: 'Print chapters even if they are empty.'
required: false
Expand Down
77 changes: 43 additions & 34 deletions release_notes_generator/action_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import json
import logging
import os
import sys

from release_notes_generator.utils.gh_action import get_action_input

Expand All @@ -26,14 +27,16 @@ class ActionInputs:
GITHUB_TOKEN = 'github-token'
TAG_NAME = 'tag-name'
CHAPTERS = 'chapters'
WARNINGS = 'warnings'
PUBLISHED_AT = 'published-at'
SKIP_RELEASE_NOTES_LABEL = 'skip-release-notes-label'
PRINT_EMPTY_CHAPTERS = 'print-empty-chapters'
CHAPTERS_TO_PR_WITHOUT_ISSUE = 'chapters-to-pr-without-issue'
VERBOSE = 'verbose'
RUNNER_DEBUG = 'RUNNER_DEBUG'

# Features
WARNINGS = 'warnings'
PRINT_EMPTY_CHAPTERS = 'print-empty-chapters'
CHAPTERS_TO_PR_WITHOUT_ISSUE = 'chapters-to-pr-without-issue'

@staticmethod
def get_github_repository() -> str:
return get_action_input(ActionInputs.GITHUB_REPOSITORY)
Expand All @@ -50,89 +53,95 @@ def get_tag_name() -> str:
def get_chapters_json() -> str:
return get_action_input(ActionInputs.CHAPTERS)

@staticmethod
def get_warnings() -> bool:
return get_action_input(ActionInputs.WARNINGS).lower() == 'true'

@staticmethod
def get_published_at() -> bool:
return get_action_input(ActionInputs.PUBLISHED_AT).lower() == 'true'
return get_action_input(ActionInputs.PUBLISHED_AT, "false").lower() == 'true'

@staticmethod
def get_skip_release_notes_label() -> str:
return get_action_input(ActionInputs.SKIP_RELEASE_NOTES_LABEL) or 'skip-release-notes'

@staticmethod
def get_print_empty_chapters() -> bool:
return get_action_input(ActionInputs.PRINT_EMPTY_CHAPTERS).lower() == 'true'
def get_verbose() -> bool:
return os.getenv('RUNNER_DEBUG', 0) == 1 or get_action_input(ActionInputs.VERBOSE).lower() == 'true'

# Features
@staticmethod
def get_chapters_to_pr_without_issue() -> bool:
return get_action_input(ActionInputs.CHAPTERS_TO_PR_WITHOUT_ISSUE).lower() == 'true'
def get_warnings() -> bool:
return get_action_input(ActionInputs.WARNINGS, "true").lower() == 'true'

@staticmethod
def get_verbose() -> bool:
return os.getenv('RUNNER_DEBUG', 0) == 1 or get_action_input(ActionInputs.VERBOSE).lower() == 'true'
def get_print_empty_chapters() -> bool:
return get_action_input(ActionInputs.PRINT_EMPTY_CHAPTERS, "true").lower() == 'true'

@staticmethod
def get_chapters_to_pr_without_issue() -> bool:
return get_action_input(ActionInputs.CHAPTERS_TO_PR_WITHOUT_ISSUE, "true").lower() == 'true'

@staticmethod
def validate_inputs():
"""
Validates the inputs provided for the release notes generator.

:raises ValueError: If any of the inputs are invalid.
Logs any validation errors and exits if any are found.
"""
errors = []

repository_id = ActionInputs.get_github_repository()
if '/' in repository_id:
owner, repo_name = ActionInputs.get_github_repository().split('/')
else:
owner = repo_name = ""

if not isinstance(owner, str) or not owner.strip():
raise ValueError("Owner must be a non-empty string.")

if not isinstance(repo_name, str) or not repo_name.strip():
raise ValueError("Repo name must be a non-empty string.")
if not isinstance(owner, str) or not owner.strip() or not isinstance(repo_name, str) or not repo_name.strip():
errors.append("Owner and Repo must be a non-empty string.")

tag_name = ActionInputs.get_tag_name()
if not isinstance(tag_name, str) or not tag_name.strip():
raise ValueError("Tag name must be a non-empty string.")
errors.append("Tag name must be a non-empty string.")

chapters_json = ActionInputs.get_chapters_json()
try:
chapters_json = ActionInputs.get_chapters_json()
json.loads(chapters_json)
except json.JSONDecodeError:
raise ValueError("Chapters JSON must be a valid JSON string.")
errors.append("Chapters JSON must be a valid JSON string.")

warnings = ActionInputs.get_warnings()
if not isinstance(warnings, bool):
raise ValueError("Warnings must be a boolean.")
errors.append("Warnings must be a boolean.")

published_at = ActionInputs.get_published_at()
if not isinstance(published_at, bool):
raise ValueError("Published at must be a boolean.")
errors.append("Published at must be a boolean.")

skip_release_notes_label = ActionInputs.get_skip_release_notes_label()
if not isinstance(skip_release_notes_label, str) or not skip_release_notes_label.strip():
raise ValueError("Skip release notes label must be a non-empty string.")
errors.append("Skip release notes label must be a non-empty string.")

verbose = ActionInputs.get_verbose()
if not isinstance(verbose, bool):
errors.append("Verbose logging must be a boolean.")

# Features
print_empty_chapters = ActionInputs.get_print_empty_chapters()
if not isinstance(print_empty_chapters, bool):
raise ValueError("Print empty chapters must be a boolean.")
errors.append("Print empty chapters must be a boolean.")

chapters_to_pr_without_issue = ActionInputs.get_chapters_to_pr_without_issue()
if not isinstance(chapters_to_pr_without_issue, bool):
raise ValueError("Chapters to PR without issue must be a boolean.")
errors.append("Chapters to PR without issue must be a boolean.")

verbose = ActionInputs.get_verbose()
if not isinstance(verbose, bool):
raise ValueError("Verbose logging must be a boolean.")
# Log errors if any
if errors:
for error in errors:
logging.error(error)
sys.exit(1)

logging.debug(f'Repository: {owner}/{repo_name}')
logging.debug(f'Tag name: {tag_name}')
logging.debug(f'Chapters JSON: {chapters_json}')
logging.debug(f'Warnings: {warnings}')
logging.debug(f'Published at: {published_at}')
logging.debug(f'Skip release notes label: {skip_release_notes_label}')
logging.debug(f'Verbose logging: {verbose}')
logging.debug(f'Warnings: {warnings}')
logging.debug(f'Print empty chapters: {print_empty_chapters}')
logging.debug(f'Chapters to PR without issue: {chapters_to_pr_without_issue}')
logging.debug(f'Verbose logging: {verbose}')
23 changes: 11 additions & 12 deletions release_notes_generator/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,33 @@
from release_notes_generator.model.custom_chapters import CustomChapters
from release_notes_generator.model.record import Record
from release_notes_generator.model.service_chapters import ServiceChapters
from release_notes_generator.action_inputs import ActionInputs


class ReleaseNotesBuilder:
def __init__(self, records: dict[int, Record], changelog_url: str,
formatter: RecordFormatter, custom_chapters: CustomChapters,
warnings: bool = True, print_empty_chapters: bool = True):
formatter: RecordFormatter, custom_chapters: CustomChapters):
"""
Constructs all the necessary attributes for the ReleaseNotesBuilder object.
Init all the necessary attributes for the ReleaseNotesBuilder object.

:param records: A dictionary of records where the key is an integer and the value is a Record object.
:param changelog_url: The URL of the changelog.
:param formatter: A RecordFormatter object used to format the records.
:param custom_chapters: A CustomChapters object representing the custom chapters.
:param warnings: A boolean indicating whether to show warnings.
:param print_empty_chapters: A boolean indicating whether to print empty chapters.
@param records: A dictionary of records where the key is an integer and the value is a Record object.
@param changelog_url: The URL of the changelog.
@param formatter: A RecordFormatter object used to format the records.
@param custom_chapters: A CustomChapters object representing the custom chapters.
"""
self.records = records
self.changelog_url = changelog_url
self.formatter = formatter
self.custom_chapters = custom_chapters
self.warnings = warnings
self.print_empty_chapters = print_empty_chapters

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

def build(self) -> str:
"""
Builds the release notes.

:return: The release notes as a string.
@return: The release notes as a string.
"""
logging.info("Building Release Notes")
user_defined_chapters = self.custom_chapters
Expand Down
4 changes: 1 addition & 3 deletions release_notes_generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import logging

from typing import Optional
from github import Github, Auth
from github import Github

from release_notes_generator.record.record_formatter import RecordFormatter
from release_notes_generator.model.custom_chapters import CustomChapters
Expand Down Expand Up @@ -75,8 +75,6 @@ def generate(self) -> Optional[str]:
records=rls_notes_records,
custom_chapters=self.custom_chapters,
formatter=RecordFormatter(),
warnings=ActionInputs.get_warnings(),
print_empty_chapters=ActionInputs.get_print_empty_chapters(),
changelog_url=changelog_url
)

Expand Down
25 changes: 11 additions & 14 deletions release_notes_generator/utils/gh_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,23 @@

import os
import sys
from typing import Optional


def get_action_input(name: str) -> str:
def get_action_input(name: str, default: Optional[str] = None) -> str:
"""
Retrieve the value of a specified input parameter from environment variables.

This function constructs the environment variable name by replacing hyphens with
underscores, converting the string to uppercase, and prefixing it with 'INPUT_'.
It then retrieves the value of this environment variable.

Args:
name (str): The name of the input parameter.
@param name: The name of the input parameter.
@param default: The default value to return if the environment variable is not set.

Returns:
str: The value of the specified input parameter, or an empty string if the environment
variable is not set.
@return: The value of the specified input parameter, or an empty string if the environment
"""
return os.getenv(f'INPUT_{name.replace("-", "_").upper()}', '')
return os.getenv(f'INPUT_{name.replace("-", "_").upper()}', default=default)


def set_action_output(name: str, value: str, default_output_path: str = "default_output.txt"):
Expand All @@ -52,11 +51,10 @@ def set_action_output(name: str, value: str, default_output_path: str = "default
This function writes the output in a specific format that includes the name of the
output and its value. The output is appended to the specified file.

Args:
name (str): The name of the output parameter.
value (str): The value of the output parameter.
default_output_path (str, optional): The default file path to which the output is
written if the 'GITHUB_OUTPUT' environment variable is not set. Defaults to "default_output.txt".
@param name: The name of the output parameter.
@param value: The value of the output parameter.
@param default_output_path: The default file path to which the output is written if the
'GITHUB_OUTPUT' environment variable is not set. Defaults to "default_output.txt".
"""
output_file = os.getenv('GITHUB_OUTPUT', default_output_path)
with open(output_file, 'a', encoding="utf-8") as f:
Expand All @@ -72,8 +70,7 @@ def set_action_failed(message: str):
This function prints an error message in the format expected by GitHub Actions
and then exits the script with a non-zero status code.

Args:
message (str): The error message to be displayed.
@param message: The error message to be displayed.
"""
print(f'::error::{message}')
sys.exit(1)
Loading
Loading