Skip to content

Commit 639531a

Browse files
Feature/150 refactor generation of rn (#151)
Create raw version of Record and split of logic to support: - all PR with issue - all PR without issue - all issues without PR - all standalone commits
1 parent 045bb20 commit 639531a

26 files changed

Lines changed: 663 additions & 265 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,6 @@ cython_debug/
157157
# and can be added to the global gitignore or merged into this file. For a more nuclear
158158
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
159159
.idea/
160+
161+
default_output.txt
162+
run_locally.sh

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ max-returns=6
322322
max-statements=50
323323

324324
# Minimum number of public methods for a class (see R0903).
325-
min-public-methods=2
325+
min-public-methods=1
326326

327327

328328
[EXCEPTIONS]

DEVELOPER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ open htmlcov/index.html
146146
```
147147

148148
## Run Action Locally
149-
Create *.sh file and place it in the project root.
149+
Create run_locally.sh file and place it in the project root.
150150

151151
```bash
152152
#!/bin/bash

main.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@
2121
"""
2222

2323
import logging
24+
import warnings
2425

2526
from github import Github, Auth
27+
from urllib3.exceptions import InsecureRequestWarning
2628

2729
from release_notes_generator.generator import ReleaseNotesGenerator
2830
from release_notes_generator.model.custom_chapters import CustomChapters
2931
from release_notes_generator.action_inputs import ActionInputs
3032
from release_notes_generator.utils.gh_action import set_action_output
3133
from release_notes_generator.utils.logging_config import setup_logging
34+
from release_notes_generator.filter import FilterByRelease
35+
36+
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
3237

3338

3439
def run() -> None:
@@ -42,7 +47,7 @@ def run() -> None:
4247
logger.info("Starting 'Release Notes Generator' GitHub Action")
4348

4449
# Authenticate with GitHub
45-
py_github = Github(auth=Auth.Token(token=ActionInputs.get_github_token()), per_page=100)
50+
py_github = Github(auth=Auth.Token(token=ActionInputs.get_github_token()), per_page=100, verify=False, timeout=60)
4651

4752
ActionInputs.validate_inputs()
4853
# Load custom chapters configuration
@@ -51,7 +56,8 @@ def run() -> None:
5156
)
5257

5358
generator = ReleaseNotesGenerator(py_github, custom_chapters)
54-
rls_notes = generator.generate()
59+
filterer = FilterByRelease()
60+
rls_notes = generator.generate(filterer)
5561
logger.debug("Generated release notes: \n%s", rls_notes)
5662

5763
# Set the output for the GitHub Action

release_notes_generator/action_inputs.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,40 @@ class ActionInputs:
6565
_row_format_issue = None
6666
_row_format_pr = None
6767
_row_format_link_pr = None
68+
_owner = ""
69+
_repo_name = ""
70+
71+
@staticmethod
72+
def get_github_owner() -> str:
73+
"""
74+
Get the GitHub owner from the action inputs.
75+
"""
76+
if ActionInputs._owner:
77+
return ActionInputs._owner
78+
79+
repository_id = get_action_input(GITHUB_REPOSITORY) or ""
80+
if "/" in repository_id:
81+
ActionInputs._owner, _ = repository_id.split("/", 1)
82+
else:
83+
ActionInputs._owner = repository_id
84+
85+
return ActionInputs._owner
86+
87+
@staticmethod
88+
def get_github_repo_name() -> str:
89+
"""
90+
Get the GitHub repository name from the action inputs.
91+
"""
92+
if ActionInputs._repo_name:
93+
return ActionInputs._repo_name
94+
95+
repository_id = get_action_input(GITHUB_REPOSITORY) or ""
96+
if "/" in repository_id:
97+
_, ActionInputs._repo_name = repository_id.split("/", 1)
98+
else:
99+
ActionInputs._repo_name = repository_id
100+
101+
return ActionInputs._repo_name
68102

69103
@staticmethod
70104
def get_github_repository() -> str:
@@ -194,7 +228,9 @@ def get_coderabbit_release_notes_title() -> str:
194228
"""
195229
Get the CodeRabbit release notes title from the action inputs.
196230
"""
197-
return get_action_input(CODERABBIT_RELEASE_NOTES_TITLE, CODERABBIT_RELEASE_NOTE_TITLE_DEFAULT) # type: ignore[return-value]
231+
return get_action_input(
232+
CODERABBIT_RELEASE_NOTES_TITLE, CODERABBIT_RELEASE_NOTE_TITLE_DEFAULT
233+
) # type: ignore[return-value]
198234

199235
@staticmethod
200236
def get_coderabbit_summary_ignore_groups() -> list[str]:
@@ -298,11 +334,16 @@ def validate_inputs() -> None:
298334
errors.append("Repository ID must be a non-empty string.")
299335

300336
if "/" in repository_id:
301-
owner, repo_name = ActionInputs.get_github_repository().split("/")
337+
ActionInputs._owner, ActionInputs._repo_name = ActionInputs.get_github_repository().split("/")
302338
else:
303-
owner = repo_name = ""
304-
305-
if not isinstance(owner, str) or not owner.strip() or not isinstance(repo_name, str) or not repo_name.strip():
339+
ActionInputs._owner = ActionInputs._repo_name = ""
340+
341+
if (
342+
not isinstance(ActionInputs._owner, str)
343+
or not ActionInputs._owner.strip()
344+
or not isinstance(ActionInputs._repo_name, str)
345+
or not ActionInputs._repo_name.strip()
346+
):
306347
errors.append("Owner and Repo must be a non-empty string.")
307348

308349
tag_name = ActionInputs.get_tag_name()
@@ -373,7 +414,7 @@ def validate_inputs() -> None:
373414
logger.error(error)
374415
sys.exit(1)
375416

376-
logger.debug("Repository: %s/%s", owner, repo_name)
417+
logger.debug("Repository: %s/%s", ActionInputs._owner, ActionInputs._repo_name)
377418
logger.debug("Tag name: %s", tag_name)
378419
logger.debug("Chapters: %s", chapters)
379420
logger.debug("Published at: %s", published_at)

release_notes_generator/builder.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
logger = logging.getLogger(__name__)
3030

3131

32-
# pylint: disable=too-few-public-methods
3332
class ReleaseNotesBuilder:
3433
"""
3534
A class representing the Release Notes Builder.
@@ -39,7 +38,7 @@ class ReleaseNotesBuilder:
3938

4039
def __init__(
4140
self,
42-
records: dict[int, Record],
41+
records: dict[int | str, Record],
4342
changelog_url: str,
4443
custom_chapters: CustomChapters,
4544
):

release_notes_generator/filter.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#
2+
# Copyright 2023 ABSA Group Limited
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
"""This module contains the Filter classes which are responsible for filtering records based on various criteria."""
18+
19+
import logging
20+
from copy import deepcopy
21+
from typing import Optional
22+
from release_notes_generator.model.mined_data import MinedData
23+
24+
logger = logging.getLogger(__name__)
25+
26+
27+
class Filter:
28+
"""
29+
Base class for filtering records.
30+
"""
31+
32+
def filter(self, data: MinedData) -> MinedData:
33+
"""
34+
Filter the mined data based on specific criteria.
35+
36+
@param data: The mined data to filter.
37+
@return: The filtered mined data.
38+
"""
39+
raise NotImplementedError("Subclasses should implement this method.")
40+
41+
42+
class FilterByRelease(Filter):
43+
"""
44+
Filter records based on the release version.
45+
"""
46+
47+
def __init__(self, release_version: Optional[str] = None):
48+
self.release_version = release_version
49+
50+
def filter(self, data: MinedData) -> MinedData:
51+
"""
52+
Filters issues, pull requests, and commits based on the latest release date.
53+
If the release is not None, it filters out closed issues, merged pull requests, and commits
54+
that occurred before the release date.
55+
56+
@Parameters:
57+
- data (MinedData): The mined data containing issues, pull requests, commits, and release information.
58+
59+
@Returns:
60+
- MinedData: The filtered mined data with issues, pull requests, and commits reduced based on the release date.
61+
"""
62+
md = MinedData()
63+
md.repository = data.repository
64+
md.release = data.release
65+
md.since = data.since
66+
67+
if data.release is not None:
68+
logger.info("Starting issue, prs and commit reduction by the latest release since time.")
69+
70+
# filter out closed Issues before the date
71+
issues_list = list(
72+
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, data.issues)
73+
)
74+
logger.debug("Count of issues reduced from %d to %d", len(data.issues), len(issues_list))
75+
76+
# filter out merged PRs and commits before the date
77+
pulls_list = list(
78+
filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, data.pull_requests)
79+
)
80+
logger.debug("Count of pulls reduced from %d to %d", len(data.pull_requests), len(pulls_list))
81+
82+
commits_list = list(filter(lambda commit: commit.commit.author.date > data.since, data.commits))
83+
logger.debug("Count of commits reduced from %d to %d", len(data.commits), len(commits_list))
84+
85+
md.issues = issues_list
86+
md.pull_requests = pulls_list
87+
md.commits = commits_list
88+
89+
logger.debug(
90+
"Input data. Issues: %d, Pull Requests: %d, Commits: %d",
91+
len(data.issues),
92+
len(data.pull_requests),
93+
len(data.commits),
94+
)
95+
logger.debug(
96+
"Filtered data. Issues: %d, Pull Requests: %d, Commits: %d",
97+
len(md.issues),
98+
len(md.pull_requests),
99+
len(md.commits),
100+
)
101+
else:
102+
md.issues = deepcopy(data.issues)
103+
md.pull_requests = deepcopy(data.pull_requests)
104+
md.commits = deepcopy(data.commits)
105+
106+
return md

release_notes_generator/generator.py

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from github import Github
2727

28-
from release_notes_generator.model.MinedData import MinedData
28+
from release_notes_generator.filter import Filter
2929
from release_notes_generator.miner import DataMiner
3030
from release_notes_generator.action_inputs import ActionInputs
3131
from release_notes_generator.builder import ReleaseNotesBuilder
@@ -65,60 +65,30 @@ def rate_limiter(self) -> GithubRateLimiter:
6565
"""Getter for the GithubRateLimiter instance."""
6666
return self._rate_limiter
6767

68-
def _filter_by_release(self, data: MinedData) -> None:
69-
"""
70-
Filters issues, pull requests, and commits based on the latest release date.
71-
If the release is not None, it filters out closed issues, merged pull requests, and commits
72-
that occurred before the release date.
73-
@param data: The mined data containing issues, pull requests, commits, and release information.
74-
"""
75-
issues_list = data.issues
76-
pulls_list = data.pull_requests
77-
commits_list = data.commits
78-
79-
if data.release is not None:
80-
logger.info("Starting issue, prs and commit reduction by the latest release since time.")
81-
82-
# filter out closed Issues before the date
83-
data.issues = list(
84-
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, issues_list)
85-
)
86-
logger.debug("Count of issues reduced from %d to %d", len(issues_list), len(data.issues))
87-
88-
# filter out merged PRs and commits before the date
89-
data.pull_requests = list(
90-
filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, pulls_list)
91-
)
92-
logger.debug("Count of pulls reduced from %d to %d", len(pulls_list), len(data.pull_requests))
93-
94-
data.commits = list(filter(lambda commit: commit.commit.author.date > data.since, commits_list))
95-
logger.debug("Count of commits reduced from %d to %d", len(commits_list), len(data.commits))
96-
97-
def generate(self) -> Optional[str]:
68+
def generate(self, filterer: Filter) -> Optional[str]:
9869
"""
9970
Generates the Release Notes for a given repository.
10071
72+
@Parameters:
73+
- filterer: An instance of Filter that will be used to filter the mined data.
74+
10175
@return: The generated release notes as a string, or None if the repository could not be found.
10276
"""
10377
miner = DataMiner(self._github_instance, self._rate_limiter)
10478
data = miner.mine_data()
10579
if data.is_empty():
10680
return None
10781

108-
self._filter_by_release(data)
82+
filtered_data = filterer.filter(data=data)
10983

11084
changelog_url: str = get_change_url(
111-
tag_name=ActionInputs.get_tag_name(), repository=data.repository, git_release=data.release
85+
tag_name=ActionInputs.get_tag_name(), repository=filtered_data.repository, git_release=filtered_data.release
11286
)
11387

114-
assert data.repository is not None, "Repository must not be None"
88+
assert filtered_data.repository is not None, "Repository must not be None"
11589

116-
rls_notes_records: dict[int, Record] = RecordFactory.generate(
117-
github=self._github_instance,
118-
repo=data.repository,
119-
issues=data.issues,
120-
pulls=data.pull_requests,
121-
commits=data.commits,
90+
rls_notes_records: dict[int | str, Record] = RecordFactory.generate(
91+
github=self._github_instance, data=filtered_data
12292
)
12393

12494
release_notes_builder = ReleaseNotesBuilder(

release_notes_generator/miner.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from github.Repository import Repository
3030

3131
from release_notes_generator.action_inputs import ActionInputs
32-
from release_notes_generator.model.MinedData import MinedData
32+
from release_notes_generator.model.mined_data import MinedData
3333
from release_notes_generator.utils.constants import ISSUE_STATE_ALL, PR_STATE_CLOSED
3434
from release_notes_generator.utils.decorators import safe_call_decorator
3535
from release_notes_generator.utils.github_rate_limiter import GithubRateLimiter
@@ -47,6 +47,9 @@ def __init__(self, github_instance: Github, rate_limiter: GithubRateLimiter):
4747
self._safe_call = safe_call_decorator(rate_limiter)
4848

4949
def mine_data(self) -> MinedData:
50+
"""
51+
Mines data from GitHub, including repository information, issues, pull requests, commits, and releases.
52+
"""
5053
logger.info("Starting data mining from GitHub...")
5154
data = MinedData()
5255

release_notes_generator/model/base_chapters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ def __init__(self, sort_ascending: bool = True, print_empty_chapters: bool = Tru
3232
self.sort_ascending = sort_ascending
3333
self.print_empty_chapters = print_empty_chapters
3434
self.chapters: dict[str, Chapter] = {}
35-
self.populated_record_numbers: list[int] = []
35+
self.populated_record_numbers: list[int | str] = []
3636

3737
@property
38-
def populated_record_numbers_list(self) -> list[int]:
38+
def populated_record_numbers_list(self) -> list[int | str]:
3939
"""
4040
Gets the list of populated record numbers.
4141
@@ -82,7 +82,7 @@ def titles(self) -> list[str]:
8282
return [chapter.title for chapter in self.chapters.values()]
8383

8484
@abstractmethod
85-
def populate(self, records: dict[int, Record]) -> None:
85+
def populate(self, records: dict[int | str, Record]) -> None:
8686
"""
8787
Populates the chapters with records.
8888

0 commit comments

Comments
 (0)