Skip to content
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

Feature/45 skip release notes should allow multiple entires #113

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
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ Generate Release Notes action is dedicated to enhance the quality and organizati
- **Required**: No
- **Default**: false

### `skip-release-notes-label`
- **Description**: Set to a label name to skip issues and PRs with this label from the release notes process generation.
### `skip-release-notes-labels`
- **Description**: List labels used for detection if issues or pull requests are ignored in the Release Notes generation process. Example: `skip-release-notes, question`.
- **Required**: No
- **Default**: `skip-release-notes`
- Notes:
- If used on issue then Issue will be skipped during Release Notes generation.
- If used on PR with issue then on PR it will be ignored and PR will show as part of issue's release notes.
- If used on PR without issue then PR will be skipped during Release Notes generation.

### `verbose`
- **Description**: Set to true to enable verbose logging for detailed output during the action's execution.
Expand Down Expand Up @@ -157,7 +161,7 @@ Add the following step to your GitHub workflow (in example are used non-default
duplicity-scope: 'service'
duplicity-icon: '🔁'
published-at: true
skip-release-notes-label: 'ignore-in-release' # changing default value of label
skip-release-notes-labels: 'ignore-in-release' # changing default value of label
verbose: false

warnings: false
Expand Down Expand Up @@ -196,7 +200,7 @@ If an issue is linked to multiple PRs, the action fetches and aggregates contrib
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.

### Enable skipping of release notes for specific issues using label
By set **skip-release-notes-label** to true the action will skip all issues and related PRs if they contain a label defined in configuration. This is useful for issues that are not relevant for release notes.
By defining the `skip-release-notes-labels` option, the action will skip all issues and related PRs if they contain a label defined in configuration. This is useful for issues that are not relevant for release notes.

### Enable Service Chapters
If the `warnings` option is enabled in the action's configuration, the release notes will include sections that highlight possible issues.
Expand Down Expand Up @@ -373,7 +377,7 @@ export INPUT_CHAPTERS='[
]'
export INPUT_WARNINGS="true"
export INPUT_PUBLISHED_AT="true"
export INPUT_SKIP_RELEASE_NOTES_LABEL="ignore-in-release"
export INPUT_SKIP_RELEASE_NOTES_LABELS="ignore-in-release"
export INPUT_PRINT_EMPTY_CHAPTERS="true"
export INPUT_VERBOSE="true"

Expand Down
6 changes: 3 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ inputs:
description: 'Use `published-at` timestamp instead of `created-at` as the reference point of previous Release.'
required: false
default: 'false'
skip-release-notes-label:
description: 'Skip label used for detection of issues or pull requests to ignore in Release Notes generation process.'
skip-release-notes-labels:
description: 'List labels used for detection if issues or pull requests are ignored in the Release Notes generation process.'
required: false
default: 'skip-release-notes'
warnings:
Expand Down Expand Up @@ -115,7 +115,7 @@ runs:
INPUT_DUPLICITY_ICON: ${{ inputs.duplicity-icon }}
INPUT_WARNINGS: ${{ inputs.warnings }}
INPUT_PUBLISHED_AT: ${{ inputs.published-at }}
INPUT_SKIP_RELEASE_NOTES_LABEL: ${{ inputs.skip-release-notes-label }}
INPUT_SKIP_RELEASE_NOTES_LABELS: ${{ inputs.skip-release-notes-labels }}
INPUT_PRINT_EMPTY_CHAPTERS: ${{ inputs.print-empty-chapters }}
INPUT_VERBOSE: ${{ inputs.verbose }}
INPUT_GITHUB_REPOSITORY: ${{ github.repository }}
Expand Down
15 changes: 7 additions & 8 deletions release_notes_generator/action_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
TAG_NAME,
CHAPTERS,
PUBLISHED_AT,
SKIP_RELEASE_NOTES_LABEL,
VERBOSE,
WARNINGS,
RUNNER_DEBUG,
Expand All @@ -39,6 +38,7 @@
ROW_FORMAT_LINK_PR,
ROW_FORMAT_ISSUE,
ROW_FORMAT_PR,
SKIP_RELEASE_NOTES_LABELS,
)
from release_notes_generator.utils.enums import DuplicityScopeEnum
from release_notes_generator.utils.gh_action import get_action_input
Expand Down Expand Up @@ -109,11 +109,14 @@ def get_published_at() -> bool:
return get_action_input(PUBLISHED_AT, "false").lower() == "true"

@staticmethod
def get_skip_release_notes_label() -> str:
def get_skip_release_notes_labels() -> list[str]:
"""
Get the skip release notes label from the action inputs.
"""
return get_action_input(SKIP_RELEASE_NOTES_LABEL) or "skip-release-notes"
user_choice = [item.strip() for item in get_action_input(SKIP_RELEASE_NOTES_LABELS, "").split(",")]
if len(user_choice) > 0:
return user_choice
return ["skip-release-notes"]

@staticmethod
def get_verbose() -> bool:
Expand Down Expand Up @@ -212,10 +215,6 @@ def validate_inputs():
published_at = ActionInputs.get_published_at()
ActionInputs.validate_input(published_at, bool, "Published at must be a boolean.", errors)

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():
errors.append("Skip release notes label must be a non-empty string.")

verbose = ActionInputs.get_verbose()
ActionInputs.validate_input(verbose, bool, "Verbose logging must be a boolean.", errors)

Expand Down Expand Up @@ -248,7 +247,7 @@ def validate_inputs():
logger.debug("Tag name: %s", tag_name)
logger.debug("Chapters JSON: %s", chapters_json)
logger.debug("Published at: %s", published_at)
logger.debug("Skip release notes label: %s", skip_release_notes_label)
logger.debug("Skip release notes labels: %s", ActionInputs.get_skip_release_notes_labels())
logger.debug("Verbose logging: %s", verbose)
logger.debug("Warnings: %s", warnings)
logger.debug("Print empty chapters: %s", print_empty_chapters)
4 changes: 4 additions & 0 deletions release_notes_generator/model/custom_chapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def populate(self, records: dict[int, Record]) -> None:
@return: None
"""
for nr in records: # iterate all records
# check if the record should be skipped
if records[nr].skip:
MobiTikula marked this conversation as resolved.
Show resolved Hide resolved
continue

for ch in self.chapters.values(): # iterate all chapters
if nr in self.populated_record_numbers_list and ActionInputs.get_duplicity_scope() not in (
DuplicityScopeEnum.CUSTOM,
Expand Down
8 changes: 7 additions & 1 deletion release_notes_generator/model/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ class Record:
A class used to represent a record in the release notes.
"""

def __init__(self, repo: Repository, issue: Optional[Issue] = None):
def __init__(self, repo: Repository, issue: Optional[Issue] = None, skip: bool = False):
self.__repo: Repository = repo
self.__gh_issue: Issue = issue
self.__pulls: list[PullRequest] = []
self.__pull_commits: dict = {}

self.__is_release_note_detected: bool = False
self.__present_in_chapters = 0
self.__skip = skip

@property
def number(self) -> int:
Expand Down Expand Up @@ -82,6 +83,11 @@ def is_present_in_chapters(self) -> bool:
"""Check if the record is present in chapters."""
return self.__present_in_chapters > 0

@property
def skip(self) -> bool:
"""Check if the record should be skipped during output generation process."""
return self.__skip

@property
def is_pr(self) -> bool:
"""Check if the record is a pull request."""
Expand Down
3 changes: 3 additions & 0 deletions release_notes_generator/model/service_chapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def populate(self, records: dict[int, Record]) -> None:
"""
# iterate all records
for nr in records:
if records[nr].skip:
continue

# skip the record when used in used and not allowed to be duplicated in Service chapters
if self.__is_row_present(nr) and not self.duplicity_allowed():
continue
Expand Down
18 changes: 13 additions & 5 deletions release_notes_generator/record/record_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from github.Repository import Repository
from github.Commit import Commit

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

from release_notes_generator.utils.decorators import safe_call_decorator
Expand Down Expand Up @@ -66,10 +67,14 @@ def create_record_for_issue(r: Repository, i: Issue) -> None:
@param i: Issue instance.
@return: None
"""
records[i.number] = Record(r, i)
# check for skip labels presence and skip when detected
issue_labels = [label.name for label in issue.labels]
skip_record = any(item in issue_labels for item in ActionInputs.get_skip_release_notes_labels())
records[i.number] = Record(r, i, skip=skip_record)

logger.debug("Created record for issue %d: %s", i.number, i.title)

def register_pull_request(pull: PullRequest):
def register_pull_request(pull: PullRequest, skip_record: bool) -> None:
for parent_issue_number in extract_issue_numbers_from_body(pull):
if parent_issue_number not in records:
logger.warning(
Expand All @@ -86,7 +91,7 @@ def register_pull_request(pull: PullRequest):
records[parent_issue_number].register_pull_request(pull)
logger.debug("Registering PR %d: %s to Issue %d", pull.number, pull.title, parent_issue_number)
else:
records[pull.number] = Record(repo)
records[pull.number] = Record(repo, skip=skip_record)
records[pull.number].register_pull_request(pull)
logger.debug(
"Registering stand-alone PR %d: %s as mentioned Issue %d not found.",
Expand Down Expand Up @@ -116,12 +121,15 @@ def register_commit_to_record(commit: Commit) -> bool:
create_record_for_issue(repo, issue)

for pull in pulls:
pull_labels = [label.name for label in pull.labels]
skip_record: bool = any(item in pull_labels for item in ActionInputs.get_skip_release_notes_labels())

if not extract_issue_numbers_from_body(pull):
records[pull.number] = Record(repo)
records[pull.number] = Record(repo, skip=skip_record)
records[pull.number].register_pull_request(pull)
logger.debug("Created record for PR %d: %s", pull.number, pull.title)
else:
register_pull_request(pull)
register_pull_request(pull, skip_record)

detected_prs_count = sum(register_commit_to_record(commit) for commit in commits)

Expand Down
2 changes: 1 addition & 1 deletion release_notes_generator/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
DUPLICITY_SCOPE = "duplicity-scope"
DUPLICITY_ICON = "duplicity-icon"
PUBLISHED_AT = "published-at"
SKIP_RELEASE_NOTES_LABEL = "skip-release-notes-label"
SKIP_RELEASE_NOTES_LABELS = "skip-release-notes-labels"
VERBOSE = "verbose"
RUNNER_DEBUG = "RUNNER_DEBUG"
ROW_FORMAT_ISSUE = "row-format-issue"
Expand Down
58 changes: 58 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ def mock_issue_closed_i1_bug(mocker):
return issue


@pytest.fixture
def mock_issue_closed_i1_bug_and_skip(mocker):
issue = mocker.Mock(spec=Issue)
issue.state = ISSUE_STATE_CLOSED
label1 = mocker.Mock(spec=MockLabel)
label1.name = "skip-release-notes"
label2 = mocker.Mock(spec=MockLabel)
label2.name = "bug"
issue.labels = [label1, label2]
issue.title = "I1+bug"
issue.number = 122
return issue


# Fixtures for GitHub Pull Request(s)
@pytest.fixture
def mock_pull_closed(mocker):
Expand All @@ -189,6 +203,28 @@ def mock_pull_closed(mocker):
return pull


@pytest.fixture
def mock_pull_closed_with_skip_label(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.url = "http://example.com/pull/123"
label1 = mocker.Mock(spec=MockLabel)
label1.name = "skip-release-notes"
pull.labels = [label1]
label2 = mocker.Mock(spec=MockLabel)
label2.name = "another-skip-label"
pull.labels = [label2]
pull.number = 123
pull.merge_commit_sha = "merge_commit_sha"
pull.title = "Fixed bug"
pull.created_at = datetime.now()
pull.updated_at = datetime.now()
pull.merged_at = None
pull.closed_at = datetime.now()
return pull


@pytest.fixture
def mock_pull_closed_with_rls_notes_101(mocker):
pull = mocker.Mock(spec=PullRequest)
Expand Down Expand Up @@ -364,6 +400,18 @@ def record_with_issue_closed_one_pull_merged(request):
return rec


@pytest.fixture
def record_with_issue_closed_one_pull_merged_skip(request):
rec = Record(
repo=(mock_repo_fixture := request.getfixturevalue("mock_repo")),
issue=request.getfixturevalue("mock_issue_closed_i1_bug_and_skip"),
skip=True,
)
rec.register_pull_request(request.getfixturevalue("mock_pull_merged"))
mock_repo_fixture.full_name = "org/repo"
return rec


@pytest.fixture
def record_with_issue_closed_two_pulls(request):
rec = Record(
Expand Down Expand Up @@ -466,6 +514,16 @@ def record_with_no_issue_one_pull_closed(request):
return record


@pytest.fixture
def record_with_no_issue_one_pull_closed_with_skip_label(request):
record = Record(repo=(mock_repo_fixture := request.getfixturevalue("mock_repo")), skip=True)
mock_repo_fixture.full_name = "org/repo"
mock_repo_fixture.draft = False
record.register_pull_request(request.getfixturevalue("mock_pull_closed_with_skip_label"))
record.register_commit(request.getfixturevalue("mock_commit"))
return record


@pytest.fixture
def record_with_no_issue_one_pull_closed_no_rls_notes(request):
record = Record(repo=request.getfixturevalue("mock_repo"))
Expand Down
5 changes: 5 additions & 0 deletions tests/release_notes/model/test_custom_chapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,21 @@ def test_populate(custom_chapters, mocker):
record1.pulls_count = 1
record1.is_present_in_chapters = False
record1.to_chapter_row.return_value = "Record 1 Chapter Row"
record1.skip = False

record2 = mocker.Mock(spec=Record)
record2.labels = ["enhancement"]
record2.pulls_count = 1
record2.is_present_in_chapters = False
record2.to_chapter_row.return_value = "Record 2 Chapter Row"
record2.skip = False

record3 = mocker.Mock(spec=Record)
record3.labels = ["feature"]
record3.pulls_count = 1
record3.is_present_in_chapters = False
record3.to_chapter_row.return_value = "Record 3 Chapter Row"
record3.skip = False

records = {
1: record1,
Expand Down Expand Up @@ -102,6 +105,7 @@ def test_populate_service_duplicity_scope(custom_chapters, mocker):
record1.pulls_count = 1
record1.is_present_in_chapters = False
record1.to_chapter_row.return_value = "Record 1 Chapter Row"
record1.skip = False

records = {
1: record1,
Expand All @@ -125,6 +129,7 @@ def test_populate_none_duplicity_scope(custom_chapters, mocker):
record1.pulls_count = 1
record1.is_present_in_chapters = False
record1.to_chapter_row.return_value = "Record 1 Chapter Row"
record1.skip = False

records = {
1: record1,
Expand Down
Loading