Skip to content

Commit 10231b4

Browse files
committed
#153 - Extract issue from PR body
- The PR related issues are now detected using API call and by scanning for supported GitHub keywords in PR body.
1 parent 5b0386e commit 10231b4

File tree

3 files changed

+42
-30
lines changed

3 files changed

+42
-30
lines changed

release_notes_generator/record/record_factory.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from release_notes_generator.utils.decorators import safe_call_decorator
3737
from release_notes_generator.utils.github_rate_limiter import GithubRateLimiter
38-
from release_notes_generator.utils.pull_request_utils import get_issues_for_pr
38+
from release_notes_generator.utils.pull_request_utils import get_issues_for_pr, extract_issue_numbers_from_body
3939

4040
logger = logging.getLogger(__name__)
4141

@@ -57,7 +57,10 @@ def generate(github: Github, data: MinedData) -> dict[int | str, Record]:
5757
"""
5858

5959
def register_pull_request(pull: PullRequest, skip_rec: bool) -> None:
60-
for parent_issue_number in safe_call(get_issues_for_pr)(pull_number=pull.number):
60+
detected_issues = extract_issue_numbers_from_body(pull)
61+
detected_issues.extend(safe_call(get_issues_for_pr)(pull_number=pull.number))
62+
63+
for parent_issue_number in detected_issues:
6164
# create an issue record if not present for PR parent
6265
if parent_issue_number not in records:
6366
logger.warning(
@@ -98,7 +101,7 @@ def register_pull_request(pull: PullRequest, skip_rec: bool) -> None:
98101
pull_labels = [label.name for label in pull.labels]
99102
skip_record: bool = any(item in pull_labels for item in ActionInputs.get_skip_release_notes_labels())
100103

101-
if not safe_call(get_issues_for_pr)(pull_number=pull.number):
104+
if not safe_call(get_issues_for_pr)(pull_number=pull.number) and not extract_issue_numbers_from_body(pull):
102105
records[pull.number] = PullRequestRecord(pull, skip=skip_record)
103106
logger.debug("Created record for PR %d: %s", pull.number, pull.title)
104107
else:

release_notes_generator/utils/pull_request_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from release_notes_generator.utils.constants import ISSUES_FOR_PRS, LINKED_ISSUES_MAX
2929

3030

31+
@lru_cache(maxsize=None)
3132
def extract_issue_numbers_from_body(pr: PullRequest) -> list[int]:
3233
"""
3334
Extracts the numbers of the issues mentioned in the body of the pull request.

tests/release_notes/test_record_factory.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,41 @@ def test_generate_with_no_commits(mocker, mock_repo):
288288
rec_issue1 = cast(IssueRecord,records[1])
289289
rec_issue2 = cast(IssueRecord,records[2])
290290

291+
# Verify that PRs are registered
292+
assert 1 == rec_issue1.pull_requests_count()
293+
assert 1 == rec_issue2.pull_requests_count()
294+
295+
assert pr1 == rec_issue2.get_pull_request(101)
296+
297+
def test_generate_with_no_commits_with_wrong_issue_number_in_pull_body_mention(mocker, mock_repo):
298+
mock_github_client = mocker.Mock(spec=Github)
299+
data = MinedData()
300+
# pylint: disable=unused-variable
301+
issue1, issue2, pr1, pr2, commit1, commit2 = setup_issues_pulls_commits(mocker)
302+
pr1.body = "Closes #100"
303+
data.issues = [issue1]
304+
data.pull_requests = [pr1] # PR linked to a non-fetched issues (due to since condition)
305+
306+
mock_rate_limit = mocker.Mock()
307+
mock_rate_limit.core.remaining = 10
308+
mock_rate_limit.core.reset.timestamp.return_value = time.time() + 3600
309+
mock_github_client.get_rate_limit.return_value = mock_rate_limit
310+
mock_repo.get_issue.return_value = issue2
311+
312+
data.commits = [] # No commits
313+
data.repository = mock_repo
314+
mocker.patch("release_notes_generator.record.record_factory.get_issues_for_pr", return_value=[2])
315+
records = RecordFactory.generate(mock_github_client, data)
316+
317+
assert 2 == len(records)
318+
319+
# Verify the record creation
320+
assert isinstance(records[1], IssueRecord)
321+
assert isinstance(records[2], IssueRecord)
322+
323+
rec_issue1 = cast(IssueRecord,records[1])
324+
rec_issue2 = cast(IssueRecord,records[2])
325+
291326
# Verify that PRs are registered
292327
assert 0 == rec_issue1.pull_requests_count()
293328
assert 1 == rec_issue2.pull_requests_count()
@@ -409,30 +444,3 @@ def mock_get_issues_for_pr_with_wrong_issue_number(pull_number: int) -> list[int
409444
elif pull_number == 102:
410445
return [2]
411446
return []
412-
413-
414-
def test_generate_with_wrong_issue_number_in_pull_body_mention(mocker, mock_repo):
415-
mocker.patch("release_notes_generator.record.record_factory.safe_call_decorator", side_effect=mock_safe_call_decorator_wrong_issue_number)
416-
mock_github_client = mocker.Mock(spec=Github)
417-
issue1, issue2, pr1, pr2, commit1, commit2 = setup_issues_pulls_commits(mocker)
418-
pr1.body = "Closes #100"
419-
data = MinedData()
420-
data.issues = [issue1, issue2]
421-
data.pull_requests = [pr1, pr2]
422-
data.commits = [commit1, commit2]
423-
data.repository = mock_repo
424-
425-
records = RecordFactory.generate(mock_github_client, data)
426-
427-
# Verify the record creation
428-
assert 3 == len(records)
429-
assert isinstance(records[1], IssueRecord)
430-
assert isinstance(records[2], IssueRecord)
431-
assert isinstance(records[101], PullRequestRecord)
432-
433-
# Verify that PRs are registered
434-
assert 0 == cast(IssueRecord, records[1]).pull_requests_count()
435-
assert 1 == cast(IssueRecord, records[2]).pull_requests_count()
436-
437-
rec_pr1 = cast(PullRequestRecord, records[101])
438-
assert 1 == rec_pr1.commits_count()

0 commit comments

Comments
 (0)