-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Feature Description
Update release-note row rendering so that when issue metadata is missing, the generator omits the entire related text fragment (prefix/label phrase), instead of printing placeholders as empty strings.
Concrete example:
-
Actual (today):
- N/A: #231 _Dependency Dashboard_ author is @renovate[bot] assigned to developed by in -
Expected (after fix):
- #231 _Dependency Dashboard_ author is @renovate[bot]
This should apply to issue rows (and, if applicable, hierarchy issue rows) generated by the Action’s row-format templates.
Problem / Opportunity
The generated release notes can contain redundant, confusing, or broken text segments when certain GitHub fields are missing:
- If an issue has no Task type, the output prepends
N/A:(noise; looks like a bug). - If an issue has no assignee, the output can contain
assigned tofollowed by nothing (dangling phrase). - If an issue has no related PR/commit/developer attribution, the output can contain
developed by in(dangling phrase).
This reduces readability and professionalism of release notes and forces manual cleanup for otherwise valid issues (e.g., Renovate bot dashboard issues).
Who benefits:
- Maintainers producing release notes (less manual editing)
- Consumers of release notes (cleaner, more readable changelogs)
- Contributors (more predictable formatting and fewer “why is this blank?” questions)
Acceptance Criteria
- For issues with no type:
- Output must not contain
N/Aand must not start withN/A:whenissue.typeis absent. - The
{type}:prefix (or equivalent prefix fragment) must be omitted entirely.
- Output must not contain
- For issues with no assignees:
- Output must not contain the phrase
assigned toat all.
- Output must not contain the phrase
- For issues with no PR/commit linkage and no derived developers:
- Output must not contain
developed bynorinfragments (nodeveloped by in).
- Output must not contain
- Existing cases where values are present must remain unchanged (no regressions).
- Unit tests must cover the new behavior for missing-field combinations (see “Additional Context”).
Proposed Solution
High-level approach:
- Adjust row rendering so templates don’t blindly
.format()into strings that include constant phrases around empty placeholders. - Implement “empty-field suppression” in the formatting layer:
- If
{type}is empty/missing → omit the entire “type prefix” fragment (not substituteN/A). - If
{assignees}is empty → omit the entire “assigned to …” fragment. - If
{developers}or{pull-requests}is empty → omit the entire “developed by … in …” fragment.
- If
- Apply consistently for:
- Issue rows (
row-format-issue) - Hierarchy issue rows (
row-format-hierarchy-issue) where the same placeholders/fragments exist.
- Issue rows (
Expected code touchpoints (permalinks):
- Issue formatting currently injects
N/Aand always emits potentially-empty fields:IssueRecord.to_chapter_row()
generate-release-notes/release_notes_generator/model/record/issue_record.py
Lines 120 to 154 in cfb1544
issue_number (int): The number of the issue. Returns: IssueRecord: The issue record with that number. """ if self._issue.number == issue_number: return self return None def to_chapter_row(self, add_into_chapters: bool = True) -> str: row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else "" format_values: dict[str, Any] = {} # collect format values format_values["type"] = f"{self._issue.type.name if self._issue.type else 'N/A'}" format_values["number"] = f"#{self._issue.number}" format_values["title"] = self._issue.title format_values["author"] = self.author format_values["assignees"] = ", ".join(self.assignees) format_values["developers"] = ", ".join(self.developers) list_pr_links = self.get_pr_links() if len(list_pr_links) > 0: format_values["pull-requests"] = ", ".join(list_pr_links) else: format_values["pull-requests"] = "" # contributors are not used in IssueRecord, so commented out for now # format_values["contributors"] = self.contributors if self.contributors is not None else "" row = f"{row_prefix}" + ActionInputs.get_row_format_issue().format(**format_values) if self.contains_release_notes(): row = f"{row}\n{self.get_rls_notes()}" return row
- Hierarchy formatting has similar fallback behavior (
type = "None") and empty-string risks:HierarchyIssueRecord.to_chapter_row()
generate-release-notes/release_notes_generator/model/record/hierarchy_issue_record.py
Lines 108 to 139 in cfb1544
for sub_hierarchy_issue in self._sub_hierarchy_issues.values(): labels.update(sub_hierarchy_issue.labels) for pull in self._pull_requests.values(): labels.update(label.name for label in pull.get_labels()) return list(labels) # methods - override ancestor methods def to_chapter_row(self, add_into_chapters: bool = True) -> str: logger.debug("Rendering hierarchy issue row for issue #%s", self.issue.number) row_prefix = f"{ActionInputs.get_duplicity_icon()} " if self.chapter_presence_count() > 1 else "" format_values: dict[str, Any] = {} # collect format values format_values["number"] = f"#{self.issue.number}" format_values["title"] = self.issue.title format_values["author"] = self.author format_values["assignees"] = ", ".join(self.assignees) format_values["developers"] = ", ".join(self.developers) if self.issue_type is not None: format_values["type"] = self.issue_type else: format_values["type"] = "None" list_pr_links = self.get_pr_links() if len(list_pr_links) > 0: format_values["pull-requests"] = ", ".join(list_pr_links) else: format_values["pull-requests"] = "" indent: str = " " * self._level
- Default templates that currently produce dangling phrases:
action.ymlrow-format defaults
generate-release-notes/action.yml
Lines 91 to 109 in cfb1544
description: 'List of "group names" to be ignored by release notes detection logic.' required: false default: '' row-format-hierarchy-issue: description: 'Format of the hierarchy issue in the release notes. Available placeholders: {type}, {number}, {title}, {author}, {assignees}, {developers}. Placeholders are case-insensitive.' required: false default: '{type}: _{title}_ {number}' row-format-issue: description: 'Format of the issue row in the release notes. Available placeholders: {type}, {number}, {title}, {author}, {assignees}, {developers}, {pull-requests}. Placeholders are case-insensitive.' required: false default: '{type}: {number} _{title}_ developed by {developers} in {pull-requests}' row-format-pr: description: 'Format of the pr row in the release notes. Available placeholders: {number}, {title}, {developers}. Placeholders are case-insensitive.' required: false default: '{number} _{title}_ developed by {developers}' row-format-link-pr: description: 'Add prefix "PR:" before link to PR when not linked an Issue.' required: false default: 'true'
Expected docs touchpoints:
- Document placeholders + clarify omission behavior when placeholders are empty:
docs/features/custom_row_formats.md
generate-release-notes/docs/features/custom_row_formats.md
Lines 1 to 24 in cfb1544
# Feature: Custom Row Formats ## Purpose Customize how individual issue, PR, and hierarchy issue lines are rendered in the release notes. Ensures output matches team conventions without post-processing. ## How It Works - Controlled by inputs: - `row-format-hierarchy-issue` - `row-format-issue` - `row-format-pr` - `row-format-link-pr` (boolean controlling prefix `PR:` presence for standalone PR links) - Placeholders are case-insensitive; unknown placeholders are removed. - Available placeholders: - Hierarchy issue rows: `{type}`, `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}` - Issue rows: `{type}`, `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}`, `{pull-requests}` - PR rows: `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}` - Duplicity icon (if triggered) is prefixed before the formatted row. ## Configuration ```yaml - name: Generate Release Notes id: release_notes_scrapper uses: AbsaOSS/generate-release-notes@v1 env:
Expected tests:
- Extend existing builder/unit tests (or add parameterized tests) to assert no dangling fragments:
tests/unit/release_notes_generator/builder/test_release_notes_builder.py
generate-release-notes/tests/unit/release_notes_generator/builder/test_release_notes_builder.py
Lines 1491 to 1545 in cfb1544
custom_chapters=custom_chapters_not_print_empty_chapters, ) actual_release_notes = builder.build() assert expected_release_notes == actual_release_notes def test_build_hierarchy_rls_notes_no_labels_no_type( mocker, mock_repo, custom_chapters_not_print_empty_chapters, mined_data_isolated_record_types_no_labels_no_type_defined ): expected_release_notes = RELEASE_NOTES_DATA_HIERARCHY_NO_LABELS_NO_TYPE mocker.patch("release_notes_generator.record.factory.default_record_factory.safe_call_decorator", side_effect=mock_safe_call_decorator) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_print_empty_chapters", return_value=True) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_hierarchy", return_value=True) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_row_format_hierarchy_issue", return_value="{type}: _{title}_ {number}") mock_github_client = mocker.Mock(spec=Github) mock_rate_limit = mocker.Mock() mock_rate_limit.rate.remaining = 10 mock_rate_limit.rate.reset.timestamp.return_value = time.time() + 3600 mock_github_client.get_rate_limit.return_value = mock_rate_limit factory = DefaultRecordFactory(github=mock_github_client, home_repository=mock_repo) records = factory.generate(mined_data_isolated_record_types_no_labels_no_type_defined) builder = ReleaseNotesBuilder( records=records, changelog_url=DEFAULT_CHANGELOG_URL, custom_chapters=custom_chapters_not_print_empty_chapters, ) actual_release_notes = builder.build() assert expected_release_notes == actual_release_notes def test_build_hierarchy_rls_notes_with_labels_no_type( mocker, mock_repo, custom_chapters_not_print_empty_chapters, mined_data_isolated_record_types_with_labels_no_type_defined ): expected_release_notes = RELEASE_NOTES_DATA_HIERARCHY_WITH_LABELS_NO_TYPE mocker.patch("release_notes_generator.record.factory.default_record_factory.safe_call_decorator", side_effect=mock_safe_call_decorator) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_print_empty_chapters", return_value=True) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_hierarchy", return_value=True) mocker.patch("release_notes_generator.builder.builder.ActionInputs.get_row_format_hierarchy_issue", return_value="{type}: _{title}_ {number}") mock_github_client = mocker.Mock(spec=Github) mock_rate_limit = mocker.Mock()
Dependencies / Related
No response
Additional Context
Relevant reference docs / constants:
- Row-format placeholder documentation:
generate-release-notes/docs/features/custom_row_formats.md
Lines 1 to 24 in cfb1544
# Feature: Custom Row Formats ## Purpose Customize how individual issue, PR, and hierarchy issue lines are rendered in the release notes. Ensures output matches team conventions without post-processing. ## How It Works - Controlled by inputs: - `row-format-hierarchy-issue` - `row-format-issue` - `row-format-pr` - `row-format-link-pr` (boolean controlling prefix `PR:` presence for standalone PR links) - Placeholders are case-insensitive; unknown placeholders are removed. - Available placeholders: - Hierarchy issue rows: `{type}`, `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}` - Issue rows: `{type}`, `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}`, `{pull-requests}` - PR rows: `{number}`, `{title}`, `{author}`, `{assignees}`, `{developers}` - Duplicity icon (if triggered) is prefixed before the formatted row. ## Configuration ```yaml - name: Generate Release Notes id: release_notes_scrapper uses: AbsaOSS/generate-release-notes@v1 env:
- Supported placeholder keys:
generate-release-notes/release_notes_generator/utils/constants.py
Lines 33 to 46 in cfb1544
CODERABBIT_SUPPORT_ACTIVE = "coderabbit-support-active" CODERABBIT_RELEASE_NOTES_TITLE = "coderabbit-release-notes-title" CODERABBIT_SUMMARY_IGNORE_GROUPS = "coderabbit-summary-ignore-groups" RUNNER_DEBUG = "RUNNER_DEBUG" ROW_FORMAT_HIERARCHY_ISSUE = "row-format-hierarchy-issue" ROW_FORMAT_ISSUE = "row-format-issue" ROW_FORMAT_PR = "row-format-pr" ROW_FORMAT_LINK_PR = "row-format-link-pr" SUPPORTED_ROW_FORMAT_KEYS_HIERARCHY_ISSUE = ["type", "number", "title", "author", "assignees", "developers"] SUPPORTED_ROW_FORMAT_KEYS_ISSUE = ["type", "number", "title", "author", "assignees", "developers", "pull-requests"] SUPPORTED_ROW_FORMAT_KEYS_PULL_REQUEST = ["number", "title", "author", "assignees", "developers"] # Features WARNINGS = "warnings"
New tests to add (proposed):
- Parameterized cases for combinations:
- missing type + no assignees + no PRs
- missing type + has assignees
- has type + missing assignees + has PRs
- has type + has developers + has PRs (control case)
- Assertions should check that the final rendered row does not include any of:
N/A:assigned to(when no assignees)developed by/in(when no developers/PRs)