Skip to content

Conversation

miroslavpojer
Copy link
Collaborator

@miroslavpojer miroslavpojer commented Aug 28, 2025

Release Notes:

  • Labels are now holded on record itself as set of strings.

Fixes #149

Summary by CodeRabbit

  • New Features
    • No user-facing changes in this release.
  • Refactor
    • Unified label handling across records for more consistent behavior and future extensibility.
  • Tests
    • Updated tests to align with the new label handling approach.
  • Chores
    • Removed an unused import to reduce maintenance overhead.

- Labels are now holded on record itself as set of strings.
@miroslavpojer miroslavpojer self-assigned this Aug 28, 2025
Copy link

coderabbitai bot commented Aug 28, 2025

Walkthrough

Consolidated label handling into the base Record class using an internal set, removed per-record public labels properties, and updated IssueRecord to merge PR labels. Tests now seed labels via internal _labels. Removed an unused import in main.py.

Changes

Cohort / File(s) Summary of changes
Label model refactor
release_notes_generator/model/record.py, release_notes_generator/model/issue_record.py, release_notes_generator/model/pull_request_record.py, release_notes_generator/model/commit_record.py
Centralized label storage in Record via _labels set and concrete labels property; removed prior abstract labels. IssueRecord now initializes _labels from issue labels and updates with PR labels on registration. PullRequestRecord and CommitRecord removed their public labels properties (PR adds internal _labels cache).
Tests update for label sourcing
tests/release_notes/test_release_notes_builder.py
Tests now provide labels via rec._labels (set of strings) instead of accessing issue.labels/pull_request.labels.
Cleanup
main.py
Removed unused import FilterByRelease.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Builder as ReleaseNotesBuilder
  participant IssueRec as IssueRecord
  participant PRRec as PullRequestRecord
  participant Base as Record (base)

  User->>Builder: build()
  Builder->>IssueRec: load issue
  Note over IssueRec: Initialize Base._labels from issue label names
  Builder->>PRRec: load PR (optional)
  Note over PRRec: Initialize Base._labels from PR label names (internal)
  Builder->>IssueRec: register(PRRec)
  Note over IssueRec: _labels ← _labels ∪ PRRec label names
  Builder->>IssueRec: labels
  IssueRec->>Builder: list(_labels)
  Builder-->>User: notes categorized by merged labels
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Merge labels from PR and the connected issue (#149)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Remove unused import of FilterByRelease (main.py) Not related to label merging between issues and PRs.

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Zejnilovic

Poem

I thump my paws on tidy ground,
Labels hop and merge around—
Issue, PR, now a set,
Neat as clover, firmly met.
Unused imports take their leave,
Notes in chapters, I believe! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/149-Merge-labels-from-PR-and-the-connected-issue

🪧 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.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or {PR Summary} to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/release_notes/test_release_notes_builder.py (1)

552-553: Replace stale .issue.labels mutation with _labels
In tests/release_notes/test_release_notes_builder.py line 552, update:

-    rec.issue.labels.append(MockLabel("enhancement"))
+    rec._labels.add("enhancement")

No other .issue.labels or .pull_request.labels mutations remain in the tests.

🧹 Nitpick comments (2)
release_notes_generator/model/issue_record.py (1)

30-31: Good: Issue labels cached and PR labels merged into the same set (meets #149). Consider using get_labels() and optional normalization.

This implements the objective to merge PR and Issue labels. For SDK compatibility, mirror the get_labels() approach here as well.

Proposed adjustments:

-        self._labels = {label.name for label in self._issue.labels}
+        if hasattr(self._issue, "get_labels"):
+            self._labels = {label.name for label in self._issue.get_labels()}
+        else:
+            self._labels = {label.name for label in getattr(self._issue, "labels", [])}
-        self._labels.update({label.name for label in pull.labels})
+        if hasattr(pull, "get_labels"):
+            self._labels.update({label.name for label in pull.get_labels()})
+        else:
+            self._labels.update({label.name for label in getattr(pull, "labels", [])})

Optionally, normalize names (e.g., .lower()) to avoid case-sensitivity mismatches with chapter label keys. I can wire that if desired.

Also applies to: 162-162

tests/release_notes/test_release_notes_builder.py (1)

714-714: Tests poke private _labels; prefer updating the existing set rather than rebinding it

Directly assigning a new set replaces the object; using update() avoids surprises and reads clearer. Longer term, consider a small helper (e.g., rec.add_labels([...])) to avoid touching a private field from tests.

Apply:

-    rec._labels = {"bug", "breaking-changes"}
+    rec._labels.update({"bug", "breaking-changes"})
-    rec._labels = {"label1", "label2"}
+    rec._labels.update({"label1", "label2"})
-    rec._labels = {"bug", "enhancement"}
+    rec._labels.update({"bug", "enhancement"})

I can add a tiny test helper or a public method to seed labels if you want to avoid accessing _labels directly.

Also applies to: 733-733, 828-828

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eda69f4 and e0cf4e4.

📒 Files selected for processing (6)
  • main.py (0 hunks)
  • release_notes_generator/model/commit_record.py (0 hunks)
  • release_notes_generator/model/issue_record.py (2 hunks)
  • release_notes_generator/model/pull_request_record.py (1 hunks)
  • release_notes_generator/model/record.py (2 hunks)
  • tests/release_notes/test_release_notes_builder.py (3 hunks)
💤 Files with no reviewable changes (2)
  • release_notes_generator/model/commit_record.py
  • main.py
🧰 Additional context used
🧬 Code graph analysis (2)
release_notes_generator/model/pull_request_record.py (1)
tests/release_notes/model/test_record.py (1)
  • labels (41-42)
release_notes_generator/model/issue_record.py (1)
tests/release_notes/model/test_record.py (1)
  • labels (41-42)
🔇 Additional comments (1)
release_notes_generator/model/record.py (1)

41-41: Good: Centralized label storage on the base class

Caching labels as a set here simplifies merging and deduplication.

Comment on lines +28 to +29
self._labels = {label.name for label in self._pull_request.labels}

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

Initialize labels via PyGithub API method for compatibility and determinism

Prefer get_labels() over the .labels attribute to avoid SDK differences and lazy-loading surprises. This also aligns with how Issue labels are typically fetched.

Proposed change:

-        self._labels = {label.name for label in self._pull_request.labels}
+        # Prefer the API method when available; fall back to attribute for mocks/tests
+        if hasattr(self._pull_request, "get_labels"):
+            self._labels = {label.name for label in self._pull_request.get_labels()}
+        else:
+            self._labels = {label.name for label in getattr(self._pull_request, "labels", [])}

If you want me to normalize casing (e.g., .lower()) for safer chapter matching, I can add that as well.

To confirm SDK behavior, please check:


🌐 Web query:

Does PyGithub PullRequest expose a `.labels` attribute or should labels be fetched via `get_labels()`?

💡 Result:

Use get_labels(). The official PyGithub PullRequest object exposes a get_labels() method (returns a PaginatedList of Label) and label-modifying methods (add_to_labels, remove_from_labels, set_labels, etc.). While some REPL/examples may show a .labels attribute in certain contexts, the documented/official way is pr.get_labels(). [1][2]

Sources:

  • PyGithub PullRequest docs (get_labels, add_to_labels, etc.). [1]
  • GitHub issue/example showing .labels in REPL but confirming get_labels() usage. [2]

Use get_labels() for deterministic label retrieval

Replace the .labels attribute with the documented get_labels() API, falling back to the attribute for mocks/tests:

-        self._labels = {label.name for label in self._pull_request.labels}
+        # Prefer the API method; fallback for mocks/tests
+        if hasattr(self._pull_request, "get_labels"):
+            self._labels = {label.name for label in self._pull_request.get_labels()}
+        else:
+            self._labels = {label.name for label in getattr(self._pull_request, "labels", [])}
📝 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
self._labels = {label.name for label in self._pull_request.labels}
# Prefer the API method; fallback for mocks/tests
if hasattr(self._pull_request, "get_labels"):
self._labels = {label.name for label in self._pull_request.get_labels()}
else:
self._labels = {label.name for label in getattr(self._pull_request, "labels", [])}
🤖 Prompt for AI Agents
In release_notes_generator/model/pull_request_record.py around lines 28-29,
replace direct access to self._pull_request.labels with the documented API: call
self._pull_request.get_labels() to retrieve labels, and fall back to the .labels
attribute for mocks/tests if get_labels is not present; then build the set with
something like {label.name for label in labels_iterable} (ensuring you treat the
result as an iterable) so label retrieval remains deterministic and compatible
with both real objects and test doubles.

Comment on lines +59 to +67
@property
def labels(self) -> list[str]:
"""
Gets the labels of the record.
Returns:
list[str]: A list of labels associated with the record.
"""
return list(self._labels)

Copy link

@coderabbitai coderabbitai bot Aug 28, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make label exposure deterministic to prevent flaky ordering

Returning a list from a set yields non-deterministic order; sorting avoids flaky tests and unstable output.

Apply:

-        return list(self._labels)
+        return sorted(self._labels)

Optionally, if you’d like a public helper to simplify tests and avoid touching _labels, consider:

# outside diff – add to Record
def add_labels(self, labels: list[str]) -> None:
    self._labels.update(labels)
🤖 Prompt for AI Agents
In release_notes_generator/model/record.py around lines 59 to 67, the labels
property returns list(self._labels) which yields non-deterministic ordering;
change the property to return a consistently ordered list by converting the set
to a sorted list (e.g., return sorted(self._labels)); optionally add a public
helper method add_labels(self, labels: list[str]) that updates the internal set
via self._labels.update(labels) to simplify tests without touching the private
attribute.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not needed.

Copy link

Choose a reason for hiding this comment

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

@miroslavpojer, understood! I'll defer to your judgment on this. The current implementation works fine for your use case.

    🐰
╭─────╮
│ ◕   ◕ │
│   ‿   │
╰─────╯

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.

Merge labels from PR and the connected issue
1 participant