Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 16, 2026

Overview

Release notes rendered dangling text fragments when issue metadata was missing, producing output like:

- N/A: #231 _Dependency Dashboard_ author is @renovate[bot] assigned to  developed by  in

This PR implements intelligent suppression of empty field fragments so missing metadata results in clean output:

- #231 _Dependency Dashboard_ author is @renovate[bot]

Release Notes

  • Fixed release note row formatting to omit text fragments when placeholders are empty (no more "N/A:", "assigned to ", "developed by in ")
  • Added intelligent empty-field suppression for issue and hierarchy issue row templates

Changes

Core Implementation

  • Added format_row_with_suppression() in release_notes_generator/utils/record_utils.py:

    • Removes {type}: prefix when type is missing (no "N/A" substitution)
    • Removes assigned to {assignees} phrase when assignees list is empty
    • Removes developed by {developers} in {pull-requests} phrase when both are empty
    • Removes in {pull-requests} suffix when only PRs are missing
    • Reuses field name constants from constants.py for maintainability
    • Uses documented regex patterns for suppression rules
  • Updated IssueRecord.to_chapter_row(): Changed type fallback from 'N/A' to '', delegates formatting to suppression helper

  • Updated HierarchyIssueRecord.to_chapter_row(): Changed type fallback from 'None' to '', delegates formatting to suppression helper

Testing & Documentation

  • Added 5 unit tests covering missing field combinations in test_issue_record_row_formatting.py, following project test style conventions and reusing existing fixtures from conftest.py
  • Updated 306 existing test expectations to reflect corrected behavior
  • Enhanced docs/features/custom_row_formats.md documenting empty field suppression behavior with examples, clarifying that suppression only applies to specific placeholders ({type}, {assignees}, {developers}, {pull-requests})

Template Coupling Note: Suppression rules target default row format patterns. Custom templates with different phrase structures may not benefit from all suppression rules.

Original prompt

This section details on the original issue you should resolve

<issue_title>Fix release note row rendering to skip missing fields (type/assignee/developers/PRs)</issue_title>
<issue_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: AbsaOSS/generate-release-notes#231 _Dependency Dashboard_ author is @renovate[bot] assigned to developed by in

  • Expected (after fix):
    - AbsaOSS/generate-release-notes#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 to followed 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/A and must not start with N/A: when issue.type is absent.
    • The {type}: prefix (or equivalent prefix fragment) must be omitted entirely.
  • For issues with no assignees:
    • Output must not contain the phrase assigned to at all.
  • For issues with no PR/commit linkage and no derived developers:
    • Output must not contain developed by nor in fragments (no developed by in).
  • 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:

  1. Adjust row rendering so templates don’t blindly .format() into strings that include constant phrases around empty placeholders.
  2. Implement “empty-field suppression” in the formatting layer:
    • If {type} is empty/missing → omit the entire “type prefix” fragment (not substitute N/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.
  3. Apply consistently for:
    • Issue rows (row-format-issue)
    • Hierarchy issue rows (row-format-hierarchy-issue) where the same placeholders/fragments exist.

Expected code touchpoints (permalinks):

  • Issue formatting currently injects N/A and always emits potentially-empty fields:
    • IssueRecord.to_chapter_row()
      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()
      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.yml row-format defaults
      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
      # 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
      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:
    • # 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:

Custom agent used: Senior Developer
Implements features and fixes with high quality, meeting specs and tests.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI and others added 4 commits January 16, 2026 11:15
Co-authored-by: miroslavpojer <109138042+miroslavpojer@users.noreply.github.com>
…sts and docs

Co-authored-by: miroslavpojer <109138042+miroslavpojer@users.noreply.github.com>
Co-authored-by: miroslavpojer <109138042+miroslavpojer@users.noreply.github.com>
…ssion

Co-authored-by: miroslavpojer <109138042+miroslavpojer@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix release note row rendering for missing fields Fix release note row rendering to skip missing fields (type/assignee/developers/PRs) Jan 16, 2026
Copilot AI requested a review from miroslavpojer January 16, 2026 11:28
Copy link
Collaborator

@miroslavpojer miroslavpojer left a comment

Choose a reason for hiding this comment

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

  • pulled
  • code review - comments created
  • local test run is passing

…ests

Co-authored-by: miroslavpojer <109138042+miroslavpojer@users.noreply.github.com>
@miroslavpojer miroslavpojer marked this pull request as ready for review January 19, 2026 09:49
Copy link
Collaborator

@tmikula-dev tmikula-dev left a comment

Choose a reason for hiding this comment

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

I have two point of my finding during the PR:

  1. You are using a lot of templates. Look if T strings in python 3.14 would be interesting topic for you.
  2. Please try to keep the clean code up and module patterns same.

Copy link
Collaborator

@tmikula-dev tmikula-dev left a comment

Choose a reason for hiding this comment

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

Please find the comments bellow. They are at this point only as suggestions what worked for me. If it is working for you, it is fine with me. But please react to my comments before merging.

- Add new sections/updates chronologically below
- Use clear headings like "## Update [date/commit]" or "## Changes Added"
- Each update should reference the commit hash that made the changes
- Purpose: Maintain full history of PR evolution for reviewers and future reference.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do find your instructions for copilot to vocal and long. Some information is repeating, since it is best to have the least chars. I would run one prompt to shorten the instructions, so no info is repeating, the bullet points are not a sentences, just points. Claude has done a great job with mine and I had great results (much better than the first try with vocal and sentences version)

Copy link
Collaborator

Choose a reason for hiding this comment

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

We made a call about it. I have tried to use your style.

@miroslavpojer miroslavpojer merged commit 33804e9 into master Feb 4, 2026
8 checks passed
@miroslavpojer miroslavpojer deleted the copilot/fix-release-note-rendering branch February 4, 2026 14:33
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.

Fix release note row rendering to skip missing fields (type/assignee/developers/PRs)

3 participants