Skip to content

Feature/150 refactor generation of rn #151

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

OlivieFranklova
Copy link
Collaborator

@OlivieFranklova OlivieFranklova commented Jul 11, 2025

Summary by CodeRabbit

  • New Features

    • Added filtering of issues, pull requests, and commits based on release version and date when generating release notes.
    • Introduced specialized record types for issues, pull requests, and commits.
    • Added a utility to retrieve issues linked to pull requests using the GitHub GraphQL API.
  • Refactor

    • Unified data handling for release notes generation, improving maintainability and flexibility.
    • Updated interfaces to accept consolidated data structures and filter objects.
  • Bug Fixes

    • Corrected module import typo in test utilities.
  • Tests

    • Added and updated tests to cover new filtering logic and record types.
    • Improved test reliability by mocking external dependencies and updating test data handling.

Checklist:

Ensure the following tasks are completed before submission.

  • My code follows the project style guidelines.
  • I have performed a self-review of my code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or my feature works.
  • New and existing tests pass locally with my changes.
  • I have used a code formatting tool to ensure consistent style.
  • I have run a linter to check for code quality issues.

Additional Context:

Add any other context about the pull request here.


Note: Remember to link this PR to the related issue by adding Fixes #issue_number to the description above.

Fixes or Closes #(issue)

@OlivieFranklova OlivieFranklova linked an issue Jul 11, 2025 that may be closed by this pull request
Copy link

coderabbitai bot commented Jul 11, 2025

Walkthrough

This update introduces a modular filtering system for release notes generation by adding a Filter abstraction and a concrete FilterByRelease class. The generator and record factory now operate on a consolidated MinedData object, and tests are updated accordingly. Additional utility functions, constants, and record subclasses are also introduced.

Changes

File(s) Change Summary
.gitignore Added default_output.txt and run_localy.sh to ignored files.
main.py Imports FilterByRelease and passes a filterer to the generator's generate method.
release_notes_generator/filter.py New module: introduces Filter (abstract) and FilterByRelease (concrete) for filtering mined data.
release_notes_generator/generator.py Refactored: removes internal filtering, accepts external Filter, updates generate signature and logic.
release_notes_generator/model/record.py Adds PullRequestRecord, IssueRecord, and CommitRecord subclasses.
release_notes_generator/record/record_factory.py Refactored: operates on MinedData, uses specific record subclasses, updates PR issue extraction logic.
release_notes_generator/action_inputs.py Adds cached owner/repo attributes and accessor methods; updates validation and logging accordingly.
release_notes_generator/utils/constants.py Adds LINKED_ISSUES_MAX and ISSUES_FOR_PRS GraphQL query template.
release_notes_generator/utils/pull_request_utils.py Adds get_issues_for_pr function for PR-linked issues via GraphQL; uses caching.
tests/release_notes/test_record_factory.py Refactored: uses MinedData, mocks PR issue retrieval, updates assertions, comments out two tests for later fix.
tests/test_filter.py New tests: unit tests for FilterByRelease filtering logic and logging.
tests/test_release_notes_generator.py Updates: passes FilterByRelease to generator, mocks PR issue retrieval in tests.
tests/utils/test_pull_reuqest_utils.py Fixes import typo; adds import for get_issues_for_pr.

Sequence Diagram(s)

sequenceDiagram
    participant Main
    participant FilterByRelease
    participant ReleaseNotesGenerator
    participant RecordFactory

    Main->>FilterByRelease: Instantiate filterer
    Main->>ReleaseNotesGenerator: generate(filterer)
    ReleaseNotesGenerator->>FilterByRelease: filter(data)
    FilterByRelease-->>ReleaseNotesGenerator: filtered data
    ReleaseNotesGenerator->>RecordFactory: generate(data)
    RecordFactory-->>ReleaseNotesGenerator: records
    ReleaseNotesGenerator-->>Main: release notes output
Loading

Possibly related PRs

  • AbsaOSS/generate-release-notes#148: Refactored filtering logic in the generator, paving the way for the external filter class and modular filtering approach introduced in this PR.

Suggested labels

enhancement

Suggested reviewers

  • benedeki
  • Zejnilovic

Poem

In the warren of code, a filter appears,
Sorting the notes as each release nears.
Issues and PRs, commits in a row—
Only the freshest are chosen to show!
With carrots of logic and burrows of test,
This bunny ensures your notes are the best.
🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or {PR Summary} to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🔭 Outside diff range comments (2)
release_notes_generator/model/record.py (1)

1-1: Apply Black formatting.

The pipeline indicates that Black formatting is required for this file.

black release_notes_generator/model/record.py
release_notes_generator/record/record_factory.py (1)

1-1: Apply Black formatting.

The pipeline indicates that Black formatting is required for this file.

black release_notes_generator/record/record_factory.py
🧹 Nitpick comments (8)
.gitignore (1)

161-161: Qualify the path or use a pattern for default_output.txt.

If the file is always produced in the repository root, prefix it with “/” to avoid accidentally ignoring identically-named files in nested directories.
Alternatively, if multiple output files can be generated, consider a pattern such as *_output.txt.

tests/test_filter.py (2)

18-18: Remove unused import.

The pytest import is not used in this file.

-import pytest
 from unittest.mock import MagicMock

46-47: Fix Yoda conditions for better readability.

The comparison order should follow natural language conventions.

-assert 0 == len(mock_log_info.call_args_list)
-assert 0 == len(mock_log_debug.call_args_list)
+assert len(mock_log_info.call_args_list) == 0
+assert len(mock_log_debug.call_args_list) == 0

-assert ('Starting issue, prs and commit reduction by the latest release since time.',) == mock_log_info.call_args_list[0][0]
-assert ('Count of issues reduced from %d to %d', 2, 1) == mock_log_debug.call_args_list[0][0]
-assert ('Count of pulls reduced from %d to %d', 2, 1) == mock_log_debug.call_args_list[1][0]
-assert ('Count of commits reduced from %d to %d', 2, 1) == mock_log_debug.call_args_list[2][0]
+assert mock_log_info.call_args_list[0][0] == ('Starting issue, prs and commit reduction by the latest release since time.',)
+assert mock_log_debug.call_args_list[0][0] == ('Count of issues reduced from %d to %d', 2, 1)
+assert mock_log_debug.call_args_list[1][0] == ('Count of pulls reduced from %d to %d', 2, 1)
+assert mock_log_debug.call_args_list[2][0] == ('Count of commits reduced from %d to %d', 2, 1)

Also applies to: 84-87

tests/release_notes/test_record_factory.py (3)

26-26: Fix import path consistency.

The import should use the full path like other imports for consistency.

-from model.MinedData import MinedData
+from release_notes_generator.model.MinedData import MinedData

187-187: Remove debug print statement.

Debug print statements should not be left in production code.

-print(records[1].__class__ )
 assert records[1].__class__ is IssueRecord

281-348: Complete the test migration.

Two test functions are commented out with TODO notes, indicating incomplete migration. These tests should either be fixed or removed to avoid confusion.

The commented tests suggest there are issues with the new MinedData interface. Would you like me to help fix these tests or should they be removed if they're no longer needed?

release_notes_generator/model/record.py (2)

253-254: Address the TODO comment and verify function usage.

The TODO comment suggests that both functions should be called and merged. However, based on the AI summary, the usage should be migrated to get_issues_for_pr. Please clarify the intended approach.

Do you want me to help implement the proper integration of both functions or migrate to the new get_issues_for_pr function?


443-475: LGTM! Well-structured inheritance with one naming concern.

The new record subclasses properly inherit from the base Record class and specialize for different GitHub entity types. The initialization logic correctly calls the parent constructor and sets the release notes detection flag.

Consider using the conventional double underscore for the ___issues attribute in PullRequestRecord:

-        self.___issues: Dict[int, List[Issue]] = {}
+        self.__issues: Dict[int, List[Issue]] = {}

Triple underscores are unusual in Python and may cause confusion. Double underscores provide proper name mangling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 045bb20 and a0f94e5.

📒 Files selected for processing (13)
  • .gitignore (1 hunks)
  • main.py (2 hunks)
  • release_notes_generator/action_inputs.py (3 hunks)
  • release_notes_generator/filter.py (1 hunks)
  • release_notes_generator/generator.py (4 hunks)
  • release_notes_generator/model/record.py (4 hunks)
  • release_notes_generator/record/record_factory.py (4 hunks)
  • release_notes_generator/utils/constants.py (1 hunks)
  • release_notes_generator/utils/pull_request_utils.py (2 hunks)
  • tests/release_notes/test_record_factory.py (7 hunks)
  • tests/test_filter.py (1 hunks)
  • tests/test_release_notes_generator.py (7 hunks)
  • tests/utils/test_pull_reuqest_utils.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
tests/utils/test_pull_reuqest_utils.py (1)
release_notes_generator/utils/pull_request_utils.py (2)
  • extract_issue_numbers_from_body (31-47)
  • get_issues_for_pr (50-64)
main.py (2)
release_notes_generator/filter.py (1)
  • FilterByRelease (40-77)
release_notes_generator/generator.py (1)
  • generate (68-98)
release_notes_generator/filter.py (2)
release_notes_generator/model/MinedData.py (1)
  • MinedData (38-50)
release_notes_generator/model/record.py (2)
  • commits (75-77)
  • issue (65-67)
release_notes_generator/model/record.py (2)
release_notes_generator/utils/pull_request_utils.py (1)
  • extract_issue_numbers_from_body (31-47)
release_notes_generator/record/record_factory.py (1)
  • register_pull_request (74-97)
release_notes_generator/action_inputs.py (1)
release_notes_generator/utils/gh_action.py (1)
  • get_action_input (26-38)
tests/release_notes/test_record_factory.py (5)
release_notes_generator/model/MinedData.py (1)
  • MinedData (38-50)
release_notes_generator/model/record.py (4)
  • Record (43-441)
  • IssueRecord (456-464)
  • commits (75-77)
  • labels (128-133)
tests/conftest.py (1)
  • mock_repo (78-81)
release_notes_generator/record/record_factory.py (2)
  • RecordFactory (41-138)
  • generate (47-138)
release_notes_generator/generator.py (1)
  • generate (68-98)
release_notes_generator/generator.py (2)
release_notes_generator/filter.py (3)
  • filter (30-37)
  • filter (48-77)
  • Filter (25-37)
release_notes_generator/record/record_factory.py (1)
  • generate (47-138)
🪛 Ruff (0.11.9)
tests/utils/test_pull_reuqest_utils.py

19-19: release_notes_generator.utils.pull_request_utils.get_issues_for_pr imported but unused

Remove unused import: release_notes_generator.utils.pull_request_utils.get_issues_for_pr

(F401)

release_notes_generator/model/record.py

24-24: typing.AnyStr imported but unused

Remove unused import: typing.AnyStr

(F401)

tests/test_filter.py

18-18: pytest imported but unused

Remove unused import: pytest

(F401)


46-46: Yoda condition detected

Rewrite as len(mock_log_info.call_args_list) == 0

(SIM300)


47-47: Yoda condition detected

Rewrite as len(mock_log_debug.call_args_list) == 0

(SIM300)


84-84: Yoda condition detected

(SIM300)


85-85: Yoda condition detected

(SIM300)


86-86: Yoda condition detected

(SIM300)


87-87: Yoda condition detected

(SIM300)

🪛 GitHub Actions: Build and Test
release_notes_generator/utils/pull_request_utils.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

release_notes_generator/filter.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

release_notes_generator/model/record.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

release_notes_generator/action_inputs.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

release_notes_generator/record/record_factory.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

release_notes_generator/generator.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

🔇 Additional comments (12)
release_notes_generator/utils/constants.py (1)

71-84: LGTM! Well-structured constants for GitHub API integration.

The new constants are properly designed:

  • LINKED_ISSUES_MAX provides a reasonable limit for API calls
  • ISSUES_FOR_PRS GraphQL query correctly uses closingIssuesReferences to fetch linked issues
  • The placeholders align with the usage in pull_request_utils.py
tests/utils/test_pull_reuqest_utils.py (1)

19-19: LGTM! Import statement corrected and expanded.

The import statement properly fixes the typo from pull_reuqest_utils to pull_request_utils and adds the new get_issues_for_pr function. The unused import warning can be ignored as this is common in test files where imports may be prepared for future tests.

main.py (2)

32-32: LGTM! Import added for new filtering mechanism.

The import correctly adds FilterByRelease to support the new external filtering approach.


55-56: LGTM! Generator updated to use new filtering interface.

The changes correctly implement the new filtering mechanism:

  • Creates a FilterByRelease instance
  • Passes it to the generate() method as required by the new signature

This aligns with the refactoring to externalize filtering logic from the generator.

tests/test_release_notes_generator.py (3)

26-26: LGTM! Import added for test integration.

The import correctly adds FilterByRelease to support testing the new filtering mechanism.


42-42: LGTM! Tests updated for new filtering interface.

All test cases have been consistently updated to pass FilterByRelease() instances to the generate() method, ensuring compatibility with the new external filtering approach.

Also applies to: 77-77, 127-127, 173-173


70-70: LGTM! Proper mocking for external dependencies.

The addition of mocking for get_issues_for_pr ensures test isolation and prevents external API calls during testing. This is a best practice for unit testing.

Also applies to: 113-113, 164-164

release_notes_generator/generator.py (1)

68-98: Excellent architectural improvement.

The refactoring to use external filtering via the Filter abstraction is a great improvement in separation of concerns and modularity. This makes the code more testable and allows for different filtering strategies.

release_notes_generator/model/record.py (1)

37-37: No import inconsistency detected in record.py

The file consistently imports and uses extract_issue_numbers_from_body—there is no reference to get_issues_for_pr, so no change is needed.

• File: release_notes_generator/model/record.py
– Line 37: imports extract_issue_numbers_from_body
– Line 254: calls extract_issue_numbers_from_body(self.__pulls[0])

Likely an incorrect or invalid review comment.

release_notes_generator/record/record_factory.py (3)

47-56: LGTM! Excellent refactoring to use MinedData.

The method signature change to accept a single MinedData object instead of separate parameters is a clean improvement that enhances data encapsulation and consistency across the codebase.


70-70: LGTM! Proper usage of specialized record classes.

The migration from generic Record to specialized IssueRecord and PullRequestRecord classes improves type safety and code clarity. The instantiation logic correctly maintains the existing functionality while leveraging the new class hierarchy.

Also applies to: 91-91, 124-124


75-75: LGTM! Consistent migration to new utility function.

The change from extract_issue_numbers_from_body to get_issues_for_pr is consistent with the refactoring goals and provides better integration with GitHub's GraphQL API for retrieving linked issues.

Also applies to: 123-123

@@ -157,3 +157,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

default_output.txt
run_localy.sh
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in ignored script name – likely ineffective rule.

run_localy.sh is probably intended to be run_locally.sh.
Because of the misspelling, the actual helper script will still be tracked (and the stray typo will mask future mistakes).

Fix the entry and keep the incorrect form for backward compatibility if it was already committed before:

-run_localy.sh
+# Correct name
+run_locally.sh
+# Legacy misspelling kept to cover any existing accidental commits
+run_localy.sh
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run_localy.sh
# Correct name
run_locally.sh
# Legacy misspelling kept to cover any existing accidental commits
run_localy.sh
🤖 Prompt for AI Agents
In the .gitignore file at line 162, the script name "run_localy.sh" is
misspelled and should be corrected to "run_locally.sh" to properly ignore the
intended file. Update the entry to include the correct spelling "run_locally.sh"
and retain the incorrect "run_localy.sh" line if it was previously committed,
ensuring backward compatibility while fixing the ignore rule.

Comment on lines +335 to 340
ActionInputs._owner, ActionInputs._repo_name = ActionInputs.get_github_repository().split("/")
else:
owner = repo_name = ""
ActionInputs._owner = ActionInputs._repo_name = ""

if not isinstance(owner, str) or not owner.strip() or not isinstance(repo_name, str) or not repo_name.strip():
if not isinstance(ActionInputs._owner, str) or not ActionInputs._owner.strip() or not isinstance(ActionInputs._repo_name, str) or not ActionInputs._repo_name.strip():
errors.append("Owner and Repo must be a non-empty string.")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent repository parsing logic.

The parsing logic differs between the new caching methods and the validation logic. When there's no "/" in the repository ID, the new methods set both owner and repo name to the full repository ID, but validation sets both to empty string.

Apply this diff to make the logic consistent:

-        if "/" in repository_id:
-            ActionInputs._owner, ActionInputs._repo_name = ActionInputs.get_github_repository().split("/")
-        else:
-            ActionInputs._owner = ActionInputs._repo_name = ""
+        ActionInputs._owner = ActionInputs.get_github_owner()
+        ActionInputs._repo_name = ActionInputs.get_github_repo_name()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ActionInputs._owner, ActionInputs._repo_name = ActionInputs.get_github_repository().split("/")
else:
owner = repo_name = ""
ActionInputs._owner = ActionInputs._repo_name = ""
if not isinstance(owner, str) or not owner.strip() or not isinstance(repo_name, str) or not repo_name.strip():
if not isinstance(ActionInputs._owner, str) or not ActionInputs._owner.strip() or not isinstance(ActionInputs._repo_name, str) or not ActionInputs._repo_name.strip():
errors.append("Owner and Repo must be a non-empty string.")
ActionInputs._owner = ActionInputs.get_github_owner()
ActionInputs._repo_name = ActionInputs.get_github_repo_name()
if not isinstance(ActionInputs._owner, str) or not ActionInputs._owner.strip() or not isinstance(ActionInputs._repo_name, str) or not ActionInputs._repo_name.strip():
errors.append("Owner and Repo must be a non-empty string.")
🤖 Prompt for AI Agents
In release_notes_generator/action_inputs.py around lines 335 to 340, the
repository parsing logic is inconsistent: the new caching methods assign both
owner and repo name to the full repository ID if no "/" is present, but the
validation sets both to empty strings. To fix this, update the validation logic
to match the caching methods by assigning both owner and repo name to the full
repository ID string when no "/" is found, ensuring consistent handling across
the code.

Comment on lines +68 to +101
_owner = ""
_repo_name = ""

@staticmethod
def get_github_owner() -> str:
"""
Get the GitHub owner from the action inputs.
"""
if ActionInputs._owner:
return ActionInputs._owner

repository_id = get_action_input(GITHUB_REPOSITORY) or ""
if "/" in repository_id:
ActionInputs._owner, _ = repository_id.split("/", 1)
else:
ActionInputs._owner = repository_id

return ActionInputs._owner

@staticmethod
def get_github_repo_name() -> str:
"""
Get the GitHub repository name from the action inputs.
"""
if ActionInputs._repo_name:
return ActionInputs._repo_name

repository_id = get_action_input(GITHUB_REPOSITORY) or ""
if "/" in repository_id:
_, ActionInputs._repo_name = repository_id.split("/", 1)
else:
ActionInputs._repo_name = repository_id

return ActionInputs._repo_name
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

Fix formatting and address logic inconsistency.

The pipeline shows Black formatting issues that need to be resolved. Additionally, there's an inconsistency in parsing logic:

  • In get_github_owner() and get_github_repo_name(): when no "/" exists, both methods set their respective values to the full repository_id
  • In validate_inputs() (line 337): when no "/" exists, both _owner and _repo_name are set to empty string

Apply this diff to fix the formatting and logic consistency:

-    _owner = ""
-    _repo_name = ""
+    _owner = ""
+    _repo_name = ""

And run Black to fix formatting issues:


🏁 Script executed:

#!/bin/bash
# Fix Black formatting issues
black release_notes_generator/action_inputs.py

Length of output: 144


🏁 Script executed:

#!/bin/bash
# Locate and display the validate_inputs method to verify parsing logic
rg -n "def validate_inputs" -A 5 -B 5 release_notes_generator/action_inputs.py

Length of output: 522


#!/bin/bash
# Display the full validate_inputs method to verify owner/repo parsing logic
rg -n "def validate_inputs" -A 50 release_notes_generator/action_inputs.py

🏁 Script executed:

#!/bin/bash
# Extract the complete validate_inputs method to inspect parsing logic
rg -n -C50 "def validate_inputs" release_notes_generator/action_inputs.py

Length of output: 4987


Ensure consistent GitHub owner/repo parsing in validate_inputs and fix formatting

The getters get_github_owner() and get_github_repo_name() fall back to the full repository string when no “/” is present, but validate_inputs() currently:

  • Calls a non-existent get_github_repository() at line 330
  • Splits and assigns _owner/_repo_name only when “/” exists, else resets both to ""
  • This diverges from the getters and will overwrite your cached values

• In release_notes_generator/action_inputs.py, update validate_inputs() (around lines 330–337):

-        repository_id = ActionInputs.get_github_repository()
-        if not isinstance(repository_id, str) or not repository_id.strip():
-            errors.append("Repository ID must be a non-empty string.")
-
-        if "/" in repository_id:
-            ActionInputs._owner, ActionInputs._repo_name = ActionInputs.get_github_repository().split("/")
-        else:
-            ActionInputs._owner = ActionInputs._repo_name = ""
+        # Use the new getters for consistent parsing
+        owner = ActionInputs.get_github_owner()
+        repo_name = ActionInputs.get_github_repo_name()
+        repository_id = f"{owner}/{repo_name}" if owner and repo_name else owner or repo_name
+        if not repository_id:
+            errors.append("Repository ID must be a non-empty string.")
+        # Persist the values returned by the getters
+        ActionInputs._owner = owner
+        ActionInputs._repo_name = repo_name

• Run Black on release_notes_generator/action_inputs.py (or install/configure Black in CI) to resolve formatting errors.

🤖 Prompt for AI Agents
In release_notes_generator/action_inputs.py around lines 330 to 337, update the
validate_inputs() function to use the existing get_github_owner() and
get_github_repo_name() methods instead of calling the non-existent
get_github_repository(). Ensure that the owner and repo name are assigned
consistently by relying on these getters rather than manually splitting the
repository string. Also, run Black code formatter on the entire file to fix any
formatting issues and maintain consistent style.


## todo romove
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove the TODO comment and outdated function.

The TODO comment indicates the extract_issue_numbers_from_body function should be removed, but it's still present in the code. If this function is no longer needed, please remove it to avoid confusion.

🤖 Prompt for AI Agents
In release_notes_generator/utils/pull_request_utils.py at line 30, remove the
TODO comment and delete the entire extract_issue_numbers_from_body function if
it is no longer needed, to keep the code clean and avoid confusion.

Comment on lines +49 to +65
@lru_cache(maxsize=None)
def get_issues_for_pr(pull_number: int) -> list[int]:
"""Update the placeholder values and formate the graphQL query"""
github_api_url = "https://api.github.com/graphql"
query = ISSUES_FOR_PRS.format(number=pull_number,
owner=ActionInputs.get_github_owner(),
name=ActionInputs.get_github_repo_name(),
first=LINKED_ISSUES_MAX)
headers = {
"Authorization": f"Bearer {ActionInputs.get_github_token()}",
"Content-Type": "application/json",
}
response = requests.post(github_api_url, json={"query": query}, headers=headers)
response.raise_for_status() # Raise an error for HTTP issues
numbers = [node['number'] for node in response.json()['data']['repository']['pullRequest']['closingIssuesReferences']['nodes']]
return numbers

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add proper error handling for GraphQL API calls.

The function lacks robust error handling for several potential failure scenarios:

  • Invalid pull request number
  • GraphQL API returning unexpected structure
  • Network failures or API rate limits

Consider adding validation and error handling:

@lru_cache(maxsize=None)
def get_issues_for_pr(pull_number: int) -> list[int]:
    """Update the placeholder values and formate the graphQL query"""
+   if not isinstance(pull_number, int) or pull_number <= 0:
+       raise ValueError(f"Invalid pull request number: {pull_number}")
+   
    github_api_url = "https://api.github.com/graphql"
    query = ISSUES_FOR_PRS.format(number=pull_number,
                                 owner=ActionInputs.get_github_owner(),
                                 name=ActionInputs.get_github_repo_name(),
                                 first=LINKED_ISSUES_MAX)
    headers = {
        "Authorization": f"Bearer {ActionInputs.get_github_token()}",
        "Content-Type": "application/json",
    }
-   response = requests.post(github_api_url, json={"query": query}, headers=headers)
-   response.raise_for_status()  # Raise an error for HTTP issues
-   numbers = [node['number'] for node in response.json()['data']['repository']['pullRequest']['closingIssuesReferences']['nodes']]
-   return numbers
+   try:
+       response = requests.post(github_api_url, json={"query": query}, headers=headers)
+       response.raise_for_status()
+       data = response.json()
+       
+       if 'errors' in data:
+           logger.warning("GraphQL errors: %s", data['errors'])
+           return []
+       
+       pr_data = data.get('data', {}).get('repository', {}).get('pullRequest')
+       if not pr_data:
+           logger.warning("Pull request %d not found", pull_number)
+           return []
+       
+       closing_issues = pr_data.get('closingIssuesReferences', {}).get('nodes', [])
+       return [node['number'] for node in closing_issues if 'number' in node]
+   except requests.exceptions.RequestException as e:
+       logger.error("Failed to fetch issues for PR %d: %s", pull_number, e)
+       return []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@lru_cache(maxsize=None)
def get_issues_for_pr(pull_number: int) -> list[int]:
"""Update the placeholder values and formate the graphQL query"""
github_api_url = "https://api.github.com/graphql"
query = ISSUES_FOR_PRS.format(number=pull_number,
owner=ActionInputs.get_github_owner(),
name=ActionInputs.get_github_repo_name(),
first=LINKED_ISSUES_MAX)
headers = {
"Authorization": f"Bearer {ActionInputs.get_github_token()}",
"Content-Type": "application/json",
}
response = requests.post(github_api_url, json={"query": query}, headers=headers)
response.raise_for_status() # Raise an error for HTTP issues
numbers = [node['number'] for node in response.json()['data']['repository']['pullRequest']['closingIssuesReferences']['nodes']]
return numbers
@lru_cache(maxsize=None)
def get_issues_for_pr(pull_number: int) -> list[int]:
"""Update the placeholder values and formate the graphQL query"""
if not isinstance(pull_number, int) or pull_number <= 0:
raise ValueError(f"Invalid pull request number: {pull_number}")
github_api_url = "https://api.github.com/graphql"
query = ISSUES_FOR_PRS.format(
number=pull_number,
owner=ActionInputs.get_github_owner(),
name=ActionInputs.get_github_repo_name(),
first=LINKED_ISSUES_MAX,
)
headers = {
"Authorization": f"Bearer {ActionInputs.get_github_token()}",
"Content-Type": "application/json",
}
try:
response = requests.post(github_api_url, json={"query": query}, headers=headers)
response.raise_for_status()
data = response.json()
if "errors" in data:
logger.warning("GraphQL errors: %s", data["errors"])
return []
pr_data = data.get("data", {}).get("repository", {}).get("pullRequest")
if not pr_data:
logger.warning("Pull request %d not found", pull_number)
return []
closing_issues = pr_data.get("closingIssuesReferences", {}).get("nodes", [])
return [node["number"] for node in closing_issues if "number" in node]
except requests.exceptions.RequestException as e:
logger.error("Failed to fetch issues for PR %d: %s", pull_number, e)
return []
🤖 Prompt for AI Agents
In release_notes_generator/utils/pull_request_utils.py around lines 49 to 65,
the function get_issues_for_pr lacks error handling for invalid pull request
numbers, unexpected GraphQL response structures, and network or API errors. Add
try-except blocks around the API call and JSON parsing to catch requests
exceptions and KeyError or TypeError for unexpected data. Validate the response
structure before accessing nested keys and handle cases where the pull request
or issues data is missing. Log or raise informative errors to make failures
clear and prevent unhandled exceptions.

Comment on lines +45 to +46
def __init__(self, release_version: Optional[str] = None):
self.release_version = release_version
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused constructor parameter.

The release_version parameter is not used anywhere in the FilterByRelease class. Either implement its usage or remove it to avoid confusion.

-def __init__(self, release_version: Optional[str] = None):
-    self.release_version = release_version
+def __init__(self):
+    pass
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self, release_version: Optional[str] = None):
self.release_version = release_version
def __init__(self):
pass
🤖 Prompt for AI Agents
In release_notes_generator/filter.py around lines 45 to 46, the constructor of
the FilterByRelease class has an unused parameter release_version. To fix this,
remove the release_version parameter from the __init__ method and also remove
the assignment to self.release_version to avoid confusion and keep the code
clean.

Comment on lines +48 to +78
def filter(self, data: MinedData):
"""
Filters issues, pull requests, and commits based on the latest release date.
If the release is not None, it filters out closed issues, merged pull requests, and commits
that occurred before the release date.
@param data: The mined data containing issues, pull requests, commits, and release information.
@return: The filtered mined data.
"""

issues_list = data.issues
pulls_list = data.pull_requests
commits_list = data.commits

if data.release is not None:
logger.info("Starting issue, prs and commit reduction by the latest release since time.")

# filter out closed Issues before the date
data.issues = list(
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, issues_list)
)
logger.debug("Count of issues reduced from %d to %d", len(issues_list), len(data.issues))

# filter out merged PRs and commits before the date
data.pull_requests = list(
filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, pulls_list)
)
logger.debug("Count of pulls reduced from %d to %d", len(pulls_list), len(data.pull_requests))

data.commits = list(filter(lambda commit: commit.commit.author.date > data.since, commits_list))
logger.debug("Count of commits reduced from %d to %d", len(commits_list), len(data.commits))

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix missing return statement and inconsistent filtering logic.

The method has two critical issues:

  1. Missing return statement - the method should return the filtered MinedData
  2. Inconsistent comparison operators for commits (>) vs issues/PRs (>=)

Apply this fix:

-def filter(self, data: MinedData):
+def filter(self, data: MinedData) -> MinedData:
     """
     Filters issues, pull requests, and commits based on the latest release date.
     If the release is not None, it filters out closed issues, merged pull requests, and commits
     that occurred before the release date.
     @param data: The mined data containing issues, pull requests, commits, and release information.
     @return: The filtered mined data.
     """

     issues_list = data.issues
     pulls_list = data.pull_requests
     commits_list = data.commits

     if data.release is not None:
         logger.info("Starting issue, prs and commit reduction by the latest release since time.")

         # filter out closed Issues before the date
         data.issues = list(
             filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, issues_list)
         )
         logger.debug("Count of issues reduced from %d to %d", len(issues_list), len(data.issues))

         # filter out merged PRs and commits before the date
         data.pull_requests = list(
             filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, pulls_list)
         )
         logger.debug("Count of pulls reduced from %d to %d", len(pulls_list), len(data.pull_requests))

-        data.commits = list(filter(lambda commit: commit.commit.author.date > data.since, commits_list))
+        data.commits = list(filter(lambda commit: commit.commit.author.date >= data.since, commits_list))
         logger.debug("Count of commits reduced from %d to %d", len(commits_list), len(data.commits))
+
+    return data
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def filter(self, data: MinedData):
"""
Filters issues, pull requests, and commits based on the latest release date.
If the release is not None, it filters out closed issues, merged pull requests, and commits
that occurred before the release date.
@param data: The mined data containing issues, pull requests, commits, and release information.
@return: The filtered mined data.
"""
issues_list = data.issues
pulls_list = data.pull_requests
commits_list = data.commits
if data.release is not None:
logger.info("Starting issue, prs and commit reduction by the latest release since time.")
# filter out closed Issues before the date
data.issues = list(
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, issues_list)
)
logger.debug("Count of issues reduced from %d to %d", len(issues_list), len(data.issues))
# filter out merged PRs and commits before the date
data.pull_requests = list(
filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, pulls_list)
)
logger.debug("Count of pulls reduced from %d to %d", len(pulls_list), len(data.pull_requests))
data.commits = list(filter(lambda commit: commit.commit.author.date > data.since, commits_list))
logger.debug("Count of commits reduced from %d to %d", len(commits_list), len(data.commits))
def filter(self, data: MinedData) -> MinedData:
"""
Filters issues, pull requests, and commits based on the latest release date.
If the release is not None, it filters out closed issues, merged pull requests, and commits
that occurred before the release date.
@param data: The mined data containing issues, pull requests, commits, and release information.
@return: The filtered mined data.
"""
issues_list = data.issues
pulls_list = data.pull_requests
commits_list = data.commits
if data.release is not None:
logger.info("Starting issue, prs and commit reduction by the latest release since time.")
# filter out closed Issues before the date
data.issues = list(
filter(lambda issue: issue.closed_at is not None and issue.closed_at >= data.since, issues_list)
)
logger.debug("Count of issues reduced from %d to %d", len(issues_list), len(data.issues))
# filter out merged PRs before the date
data.pull_requests = list(
filter(lambda pull: pull.merged_at is not None and pull.merged_at >= data.since, pulls_list)
)
logger.debug("Count of pulls reduced from %d to %d", len(pulls_list), len(data.pull_requests))
# filter out commits before the date (using >= for consistency)
data.commits = list(
filter(lambda commit: commit.commit.author.date >= data.since, commits_list)
)
logger.debug("Count of commits reduced from %d to %d", len(commits_list), len(data.commits))
return data
🤖 Prompt for AI Agents
In release_notes_generator/filter.py between lines 48 and 78, the filter method
lacks a return statement and uses inconsistent comparison operators for
filtering commits versus issues and pull requests. Fix this by adding a return
statement at the end to return the filtered data object, and change the commit
filtering comparison operator from '>' to '>=' to match the inclusive filtering
logic used for issues and pull requests.

@@ -105,7 +76,7 @@ def generate(self) -> Optional[str]:
if data.is_empty():
return None

self._filter_by_release(data)
filterer.filter(data=data)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the return value from the filter method.

The filter method returns the filtered MinedData object, but the code doesn't use the return value. While the current implementation works because the filter modifies the data in-place, it's better to follow the contract and use the return value.

-filterer.filter(data=data)
+data = filterer.filter(data=data)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
filterer.filter(data=data)
data = filterer.filter(data=data)
🤖 Prompt for AI Agents
In release_notes_generator/generator.py at line 79, the filter method's return
value, which is the filtered MinedData object, is not being used. Update the
code to assign the result of filterer.filter(data=data) back to a variable
(e.g., data) to properly use the returned filtered object instead of relying on
in-place modification.

@@ -21,7 +21,7 @@
import logging
import re

from typing import Optional, AnyStr, Any
from typing import Optional, AnyStr, Any, Dict, List
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused import.

The AnyStr import is not used in the file and should be removed.

-from typing import Optional, AnyStr, Any, Dict, List
+from typing import Optional, Any, Dict, List
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from typing import Optional, AnyStr, Any, Dict, List
from typing import Optional, Any, Dict, List
🧰 Tools
🪛 Ruff (0.11.9)

24-24: typing.AnyStr imported but unused

Remove unused import: typing.AnyStr

(F401)

🤖 Prompt for AI Agents
In release_notes_generator/model/record.py at line 24, the import statement
includes AnyStr which is not used anywhere in the file. Remove AnyStr from the
import list to clean up unused imports and improve code clarity.

from github.Commit import Commit

from model.record import IssueRecord, PullRequestRecord
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix import path for record subclasses.

The import statement is missing the full module path. It should include the release_notes_generator prefix to match the other imports.

-from model.record import IssueRecord, PullRequestRecord
+from release_notes_generator.model.record import IssueRecord, PullRequestRecord
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from model.record import IssueRecord, PullRequestRecord
from release_notes_generator.model.record import IssueRecord, PullRequestRecord
🤖 Prompt for AI Agents
In release_notes_generator/record/record_factory.py at line 28, the import
statement for IssueRecord and PullRequestRecord is missing the full module path.
Update the import to include the release_notes_generator prefix so it matches
the other imports, changing it to import from
release_notes_generator.model.record instead of just model.record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refactor generation of RN
1 participant