Skip to content

Add support for linking issues from an erratum#313

Merged
kkaarreell merged 2 commits intomainfrom
ks_link_erratum_issues
Feb 7, 2026
Merged

Add support for linking issues from an erratum#313
kkaarreell merged 2 commits intomainfrom
ks_link_erratum_issues

Conversation

@kkaarreell
Copy link
Collaborator

@kkaarreell kkaarreell commented Feb 4, 2026

Summary by Sourcery

Integrate erratum-associated Jira issues into Jira linking workflows and enhance link configuration flexibility and safety.

New Features:

  • Populate Erratum objects with Jira issue keys fetched from Errata Tool and expose them for use in Jira workflows.
  • Support dynamic link definitions in Jira issue recipes using template references that can resolve to lists, such as an erratum's Jira issues.

Enhancements:

  • Refactor Jira issue link creation into a dedicated helper that avoids duplicate links, validates target issue existence, and logs link operations.
  • Extend IssueHandler to accept a logger instance for improved diagnostics when managing Jira links.
  • Improve documentation for the links configuration, including new supported formats and behavior details.

Tests:

  • Add unit tests covering template-based link rendering for list values, lists of templates, invalid link configurations, and population of jira_issues from Errata Tool into event state files.

@kkaarreell kkaarreell self-assigned this Feb 4, 2026
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 4, 2026

Reviewer's Guide

Adds support for fetching Jira issues from ErrataTool into the Erratum model, extends Jira link configuration/rendering to handle dynamic list-based templates, refactors Jira link creation into a dedicated method with logging and validation, wires logging into the Jira handler, and updates tests and README to cover the new behavior.

Sequence diagram for Jira issue creation with validated dynamic links

sequenceDiagram
    actor User
    participant JiraCLI as JiraCLICommand
    participant Helpers as JiraHelpers
    participant Handler as IssueHandler
    participant Jira as JiraAPI

    User->>JiraCLI: Run cmd_jira with config
    JiraCLI->>Handler: Initialize IssueHandler(group, board, logger)

    JiraCLI->>Helpers: _render_action_fields(action, artifact_job, jira_handler, jira_event_fields)
    Helpers->>Helpers: Render fields and links (including templates)
    Helpers-->>JiraCLI: rendered_fields, rendered_links

    JiraCLI->>Handler: create_issue(summary, description, rendered_fields, rendered_links, transition_name)
    Handler->>Jira: create_issue
    Jira-->>Handler: jira_issue
    Handler->>Jira: update_issue_fields
    Jira-->>Handler: ok
    Handler->>Handler: Build Issue instance new_issue

    alt links_present
        Handler->>Handler: add_issue_links(new_issue, rendered_links)
        Handler->>Handler: get_details(new_issue)
        Handler-->>Handler: existing_links
        loop each relation and linked_key
            Handler->>Jira: issue(linked_key) to verify existence
            alt issue_exists_and_not_linked
                Handler->>Jira: create_issue_link(type, new_issue.id, linked_key)
                Jira-->>Handler: ok
                Handler->>Handler: log info via logger
            else already_linked_or_missing
                Handler->>Handler: log debug via logger
            end
        end
    end

    Handler-->>JiraCLI: new_issue
    JiraCLI-->>User: Report created or reused issue with links
Loading

Class diagram for updated Jira and Errata handling

classDiagram
    class IssueHandler {
        Optional~str~ group
        Optional~str_int~ board
        Optional~logging_Logger~ logger
        connection() jira_JIRA
        create_issue(summary, description, fields, links, transition_name) Issue
        add_issue_links(issue, links) void
        get_details(issue) jira_Issue
        drop_obsoleted_issue(issue, obsoleted_by) void
    }

    class ErrataService {
        url str
        fetch_info(erratum_id) JSON
        fetch_releases(erratum_id) JSON
        fetch_blocking_errata(erratum_id) JSON
        fetch_jira_issues(erratum_id) JSON
        get_errata(event, process_blocking_errata) list~Erratum~
    }

    class Erratum {
        id str
        product Optional~str~
        release Optional~str~
        status Optional~str~
        people_assigned_to Optional~str~
        people_package_owner Optional~str~
        people_qe_group Optional~str~
        people_devel_group Optional~str~
        revision Optional~int~
        jira_issues list~str~
    }

    class JiraHelpers {
        _render_action_value(value, artifact_job, action, jira_event_fields) str
        _render_action_fields(action, artifact_job, jira_handler, jira_event_fields) tuple
    }

    class JiraCLICommand {
        cmd_jira(ctx, config) void
    }

    IssueHandler "1" --> "*" Erratum : uses_in_templates
    ErrataService "1" --> "*" Erratum : creates
    JiraHelpers --> Erratum : reads_jira_issues
    JiraHelpers --> IssueHandler : passes_rendered_links
    JiraCLICommand --> IssueHandler : constructs_with_logger
    ErrataService --> ErrataToolAPI : calls

    class ErrataToolAPI {
        fetch_jira_issues(erratum_id) JSON
    }
Loading

Flow diagram for fetching erratum Jira issues and using them in dynamic links

flowchart LR
    subgraph ErrataTool
        ET_API["ErrataTool API"]
    end

    subgraph NEWA
        ES["ErrataService.get_errata"]
        ERR["Erratum model (jira_issues)"]
        JH["JiraHelpers._render_action_fields"]
        IH["IssueHandler.add_issue_links"]
    end

    subgraph Jira
        JIRA_API["Jira REST API"]
    end

    ET_API -->|"fetch_jira_issues(erratum_id)"| ES
    ES -->|jira_issues list| ERR

    ERR -->|ERRATUM.jira_issues in templates| JH
    JH -->|"rendered_links (lists of issue keys)"| IH

    IH -->|verify target issue| JIRA_API
    IH -->|create_issue_link| JIRA_API
Loading

File-Level Changes

Change Details Files
Expose Jira issues from ErrataTool on the Erratum model and persist them into event YAML for use in workflows.
  • Add ErrataTool.fetch_jira_issues to call /advisory//jira_issues.json and return JSON
  • Update get_errata to fetch jira_issues for the event’s erratum, extract issue keys, and pass them into Erratum construction
  • Extend Erratum model with a jira_issues list[str] field and ensure it is serialized into event files
  • Add CLI test fixture mock_et_fetch_jira_issues and a test that asserts jira_issues is populated in generated event YAML
newa/services/errata_service.py
newa/models/artifacts.py
tests/unit/test_cli.py
Refactor Jira issue link creation into a reusable method that deduplicates links, validates targets, and logs operations.
  • Add logger attribute to IssueHandler and inject it from the Jira CLI command
  • Refactor IssueHandler.create_issue to construct Issue first and then delegate link creation to a new add_issue_links method
  • Implement IssueHandler.add_issue_links to prefetch existing links, skip already linked issues, verify target issues exist via JIRA API, create links in the correct inward/outward direction, and log debug/info messages; raise on unknown link types or linking failures
  • Ensure _find_or_create_issue also calls add_issue_links when reusing existing issues
newa/services/jira_service.py
newa/cli/commands/jira_cmd.py
newa/cli/jira_helpers.py
Enhance Jira link configuration and template rendering to support static lists, lists of templates, and dynamic list-valued templates, with better validation.
  • Expand _render_action_value docstring but keep behavior as Jinja template renderer
  • Extend _render_action_fields link rendering to handle: a string value treated as a template that may evaluate to a list (parsed via YAML) or scalar, and a list of template strings evaluated individually
  • Validate link configuration types, raising exceptions when links values are neither string nor list, or when list elements are not strings
  • Ensure rendered_links is always a dict[str, list[str]] consumable by IssueHandler.add_issue_links
newa/cli/jira_helpers.py
Update documentation and tests to cover dynamic link lists and validation behavior.
  • Document advanced link formats in README, including static lists, lists of templates, and dynamic list references like "{{ ERRATUM.jira_issues }}", plus the behavior of link validation and skipping invalid/duplicate links
  • Add tests for rendering a template that resolves to a list and ensuring the result is parsed as a native list
  • Add tests for rendering lists of template strings into link keys and for rejecting invalid link types in configuration
README.md
tests/unit/test_cli.py

Possibly linked issues

  • #unknown: PR fetches erratum Jira issues and supports templated links like “is related to”, implementing requested automatic linkage.
  • #RFE-issue-linking: PR implements the requested issue-linking config, supporting YAML-defined relations and dynamic Jira issue lists from errata.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • The return_native branch in _render_action_value manually strips {{ }} and walks attributes via getattr, which won’t handle common Jinja2 use cases (filters, indexing, dict access, method calls, etc.); consider instead using a NativeEnvironment or otherwise evaluating the template directly via Jinja to preserve full expression support and avoid duplicating its semantics.
  • In add_issue_links, issue_details.fields.issuelinks is accessed unconditionally; if issuelinks is missing on the Jira issue, this will raise an AttributeError, so it would be safer to use getattr(issue_details.fields, 'issuelinks', []) before iterating.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `return_native` branch in `_render_action_value` manually strips `{{ }}` and walks attributes via `getattr`, which won’t handle common Jinja2 use cases (filters, indexing, dict access, method calls, etc.); consider instead using a `NativeEnvironment` or otherwise evaluating the template directly via Jinja to preserve full expression support and avoid duplicating its semantics.
- In `add_issue_links`, `issue_details.fields.issuelinks` is accessed unconditionally; if `issuelinks` is missing on the Jira issue, this will raise an `AttributeError`, so it would be safer to use `getattr(issue_details.fields, 'issuelinks', [])` before iterating.

## Individual Comments

### Comment 1
<location> `newa/services/jira_service.py:44` </location>
<code_context>

     group: Optional[str] = field(default=None)
     board: Optional[Union[str, int]] = field(default=None)
+    logger: Optional[Any] = field(default=None)

     @property
</code_context>

<issue_to_address>
**suggestion:** Use a more specific logger type instead of `Any` for better type safety.

Since this field is always a logger, annotate it as something like `Optional[logging.Logger]` or `Optional[logging.LoggerAdapter]` so type checkers and IDEs can validate logger usage instead of accepting arbitrary objects.

Suggested implementation:

```python
    group: Optional[str] = field(default=None)
    board: Optional[Union[str, int]] = field(default=None)
    logger: Optional["logging.Logger"] = field(default=None)

```

` section.

Here are the edits:

<file_operations>
<file_operation operation="edit" file_path="newa/services/jira_service.py">
<<<<<<< SEARCH
    group: Optional[str] = field(default=None)
    board: Optional[Union[str, int]] = field(default=None)
    logger: Optional[Any] = field(default=None)
=======
    group: Optional[str] = field(default=None)
    board: Optional[Union[str, int]] = field(default=None)
    logger: Optional["logging.Logger"] = field(default=None)
>>>>>>> REPLACE
</file_operation>
</file_operations>

<additional_changes>
1. Ensure `logging` is imported at the top of `newa/services/jira_service.py`, for example:
   - `import logging`
2. If you prefer supporting both plain loggers and logger adapters, you can instead use:
   - `logger: Optional[Union["logging.Logger", "logging.LoggerAdapter"]] = field(default=None)`
   and import `Union` if not already imported.
3. If `Any` is not used elsewhere in this file, you can remove it from any `from typing import ...` import list.
</issue_to_address>

### Comment 2
<location> `newa/services/jira_service.py:453-459` </location>
<code_context>
+            return
+
+        # Fetch existing issue links once before the loop
+        issue_details = self.get_details(issue)
+        existing_links = issue_details.fields.issuelinks
+        short_sleep()
+
+        # Build set of already linked issue keys for fast lookup
+        existing_linked_keys = set()
+        for link in existing_links:
+            if hasattr(link, 'inwardIssue'):
+                existing_linked_keys.add(link.inwardIssue.key)
</code_context>

<issue_to_address>
**issue:** Guard against missing or `None` `issuelinks` to avoid runtime errors.

`existing_links` is assumed iterable, but in some Jira setups `fields.issuelinks` may be missing or `None`, causing `TypeError` in the `for` loop. Consider normalizing, e.g. `existing_links = getattr(issue_details.fields, "issuelinks", []) or []`, so the loop safely handles cases with no links.
</issue_to_address>

### Comment 3
<location> `newa/cli/jira_helpers.py:89` </location>
<code_context>
+        return_native: bool = False) -> Any:
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The manual expression resolution in `return_native` mode is limited and may produce surprising results for complex templates.

In the `return_native` path, the expression is resolved by splitting on `'.'` and walking attributes, so it only handles simple chains like `ERRATUM.jira_issues`. More complex Jinja expressions (indexes, filters, method calls, etc.) fall back to `template.render`, which returns a string and can silently change the expected type (e.g., list vs string). Please either document that `return_native` only supports simple attribute access, or switch to a more robust native-evaluation approach (e.g., Jinja’s `NativeEnvironment` or `compile_expression`) so results are consistently native-typed.

```suggestion
        return_native: If True, return native type (e.g., list) instead of string representation.
            Only simple dotted attribute paths (e.g., ``ERRATUM.jira_issues``) are resolved
            natively by walking attributes. More complex Jinja expressions (indexes, filters,
            method calls, etc.) are rendered via ``template.render`` and will return a string.
```
</issue_to_address>

### Comment 4
<location> `tests/unit/test_cli.py:66-68` </location>
<code_context>
         """ Return empty json for blocking errata """
         return {}

+    def mock_et_fetch_jira_issues(self, id: str):
+        """ Return empty list for jira issues """
+        return []
+
     def mock_et_fetch_system_info(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Add assertions that `Erratum.jira_issues` is correctly populated and passed through to the CLI logic

`mock_et_fetch_jira_issues` always returns an empty list, and there are no assertions around `Erratum.jira_issues` or its use in the CLI (e.g. `_render_action_fields` with `{{ ERRATUM.jira_issues }}` in `action.links`). Please add or extend a test that:

- sets `mock_et_fetch_jira_issues` to return a non-empty payload (e.g. `[{"key": "JIRA-1"}, {"key": "JIRA-2"}]`),
- exercises the path that builds `Erratum` objects via `get_errata` and processes an action whose links reference `{{ ERRATUM.jira_issues }}`, and
- asserts the rendered links include the expected Jira keys.

This will verify the new endpoint is correctly wired into `Erratum` and the JIRA CLI flow.

Suggested implementation:

```python
    def mock_et_fetch_jira_issues(self, id: str):
        """ Return list for jira issues used to populate Erratum.jira_issues in CLI tests """
        return [{"key": "JIRA-1"}, {"key": "JIRA-2"}]

```

To fully implement your review comment, you will also need to:
1. Add or extend a CLI test that:
   - Uses the existing fixture/setup that wires `fetch_jira_issues` to `mock_et_fetch_jira_issues`.
   - Calls the CLI command (or helper) that internally invokes `get_errata` and then processes an action whose `links` include a template reference to `{{ ERRATUM.jira_issues }}` (for example, via `_render_action_fields`).
   - Asserts the rendered CLI output (e.g., printed links or returned structure) contains both `"JIRA-1"` and `"JIRA-2"` in the expected place.
2. Place this new test at module scope in `tests/unit/test_cli.py`, alongside the other CLI tests, ensuring it reuses existing fixtures rather than recreating them.

Because only a small fragment of `tests/unit/test_cli.py` is visible here, you will need to:
- Find the existing test that exercises actions/links rendering (it likely already asserts on other `ERRATUM` fields),
- Extend its assertions to check that rendered links include `JIRA-1` and `JIRA-2`, or
- Create a new test (e.g. `test_cli_renders_jira_issues_in_action_links`) that calls the same code path and adds the appropriate assertions.
</issue_to_address>

### Comment 5
<location> `tests/unit/test_cli.py:83` </location>
<code_context>
     monkeypatch.setattr(newa.ErrataTool, 'fetch_info', mock_et_fetch_info)
     monkeypatch.setattr(newa.ErrataTool, 'fetch_releases', mock_et_fetch_releases)
     monkeypatch.setattr(newa.ErrataTool, 'fetch_blocking_errata', mock_et_fetch_blocking_errata)
+    monkeypatch.setattr(newa.ErrataTool, 'fetch_jira_issues', mock_et_fetch_jira_issues)
     monkeypatch.setattr(newa.ErrataTool, 'fetch_system_info', mock_et_fetch_system_info)

</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for link rendering when `action.links` is a template string resolving to a list and for invalid link configurations

Given the new `_render_action_value(..., return_native=True)` behavior and updated `_render_action_fields`, please add tests that cover:

1. `action.links["rel"] = "{{ ERRATUM.jira_issues }}"` where `ERRATUM.jira_issues` is a list, asserting `_render_action_fields` returns that list unchanged.
2. `action.links["rel"]` as a list of template strings, asserting each element is rendered and collected correctly.
3. Invalid types for `action.links["rel"]` (e.g., `42`, `{}`), asserting an exception is raised with the expected message.

This will lock in the new link templating semantics and prevent regressions.

Suggested implementation:

```python
monkeypatch.setattr(newa.ErrataTool, 'fetch_blocking_errata', mock_et_fetch_blocking_errata)

# New tests for link rendering and validation
def test_render_action_links_rel_template_resolves_to_list():
    """
    When action.links["rel"] is a single template string whose rendered value
    is a list, _render_action_fields should return that list unchanged.
    """
    from elliottlib.cli import _render_action_fields  # adjust import if different

    # Context with ERRATUM.jira_issues as a list
    context = {
        "ERRATUM": {
            "jira_issues": ["JIRA-1", "JIRA-2"],
        }
    }

    # action.links["rel"] is a template that resolves to the jira_issues list
    action = {
        "links": {
            "rel": "{{ ERRATUM.jira_issues }}",
        }
    }

    rendered = _render_action_fields(action, context)

    assert "links" in rendered
    assert "rel" in rendered["links"]
    # The rendered rel should be exactly the list from context
    assert rendered["links"]["rel"] == ["JIRA-1", "JIRA-2"]


def test_render_action_links_rel_list_of_templates():
    """
    When action.links["rel"] is a list of template strings, each element
    should be rendered and collected into the resulting list.
    """
    from elliottlib.cli import _render_action_fields  # adjust import if different

    context = {
        "ERRATUM": {
            "jira_issues": ["JIRA-1", "JIRA-2"],
        }
    }

    # Each element is a template pointing at an individual jira issue
    action = {
        "links": {
            "rel": [
                "{{ ERRATUM.jira_issues[0] }}",
                "{{ ERRATUM.jira_issues[1] }}",
            ],
        }
    }

    rendered = _render_action_fields(action, context)

    assert "links" in rendered
    assert "rel" in rendered["links"]
    assert rendered["links"]["rel"] == ["JIRA-1", "JIRA-2"]


def test_render_action_links_rel_invalid_types_raise():
    """
    Invalid types for action.links["rel"] should raise with an informative message.
    """
    import pytest
    from elliottlib.cli import _render_action_fields  # adjust import if different

    context = {
        "ERRATUM": {
            "jira_issues": ["JIRA-1"],
        }
    }

    # rel is an int -> invalid
    action_int = {
        "links": {
            "rel": 42,
        }
    }

    # rel is a dict -> invalid
    action_dict = {
        "links": {
            "rel": {"foo": "bar"},
        }
    }

    with pytest.raises((TypeError, ValueError), match=r"links\['rel'\]"):
        _render_action_fields(action_int, context)

    with pytest.raises((TypeError, ValueError), match=r"links\['rel'\]"):
        _render_action_fields(action_dict, context)

```

1. Adjust the import path for `_render_action_fields` at the top of the new tests if it lives in a different module than `elliottlib.cli`.
2. If the real error type or message for invalid `links["rel"]` is different, update the `pytest.raises` tuple and `match` argument to match the actual behavior (e.g. a specific custom exception class or a more precise error message).
3. Ensure that any required fixtures or helpers used by other tests (e.g. when they construct `action` objects) are reused here if your test suite prefers a specific `action` structure instead of plain dicts; in that case, replace the literal `action = {...}` with calls to those helpers.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@kkaarreell kkaarreell force-pushed the ks_link_erratum_issues branch from bd8ebff to be78b33 Compare February 4, 2026 14:16
@kkaarreell
Copy link
Collaborator Author

@sourcery-ai review

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new _render_action_value branch that uses NativeEnvironment creates a fresh Jinja environment without any of the filters/extensions used by render_template; consider wiring it through the same environment (or copying its configuration/filters) so templates behave consistently regardless of return_native.
  • In _render_action_fields, when a string-valued links[relation] renders to a list via return_native=True, the resulting list is passed through unchanged; you may want to validate/normalize each element to a string to avoid accidentally propagating non-string values as issue keys.
  • test_erratum_jira_issues_populated never actually asserts that jira_issues is present or has the expected values (the assertion is commented as “might not be in the YAML”); it would be clearer either to assert the intended behavior explicitly or to narrow the test’s scope to what is actually guaranteed today.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `_render_action_value` branch that uses `NativeEnvironment` creates a fresh Jinja environment without any of the filters/extensions used by `render_template`; consider wiring it through the same environment (or copying its configuration/filters) so templates behave consistently regardless of `return_native`.
- In `_render_action_fields`, when a string-valued `links[relation]` renders to a list via `return_native=True`, the resulting list is passed through unchanged; you may want to validate/normalize each element to a string to avoid accidentally propagating non-string values as issue keys.
- `test_erratum_jira_issues_populated` never actually asserts that `jira_issues` is present or has the expected values (the assertion is commented as “might not be in the YAML”); it would be clearer either to assert the intended behavior explicitly or to narrow the test’s scope to what is actually guaranteed today.

## Individual Comments

### Comment 1
<location> `tests/unit/test_cli.py:786-787` </location>
<code_context>
+            yaml_data = yaml_parser().load(event_file.read_text())
+            erratum = yaml_data.get('erratum')
+            assert erratum is not None
+            # The jira_issues should be populated by the ErrataTool fetch
+            # Note: Currently this might not be in the YAML, as it depends on
+            # whether ErrataTool.fetch_jira_issues is being called during event processing
+
</code_context>

<issue_to_address>
**issue (testing):** This test does not currently assert that `jira_issues` are present/populated, so it doesn't verify the behavior described in its name and docstring.

As written, this will pass even if `jira_issues` is never written to the YAML or is empty. Please either:

- add assertions on `erratum['jira_issues']` (that it exists and matches the values from `_mock_errata_tool`, e.g. `['JIRA-1', 'JIRA-2']`), or
- if `jira_issues` is not expected in the YAML yet, adjust the test name/docstring or mark it `xfail` with a clear reason to avoid a false sense of coverage.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@kkaarreell kkaarreell force-pushed the ks_link_erratum_issues branch from be78b33 to b3ae582 Compare February 4, 2026 14:28
@kkaarreell kkaarreell force-pushed the ks_link_erratum_issues branch 2 times, most recently from 71fa8a7 to e8a2b45 Compare February 5, 2026 18:18
@kkaarreell
Copy link
Collaborator Author

@sourcery-ai review

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In IssueHandler.add_issue_links, existing links are de-duplicated solely by issue key, so you will skip creating a link with a different relation type to an already-linked issue; if multiple relations to the same key should be allowed, consider tracking existing links by both relation and key.
  • The per-key existence check in add_issue_links (self.connection.issue(linked_key)) can become expensive for configurations with many links; you may want to make this validation optional or narrow the exception handling to only skip on 404-like errors while surfacing other failures.
  • In ErrataTool.get_errata, fetch_jira_issues is assumed to return a list of dicts with a key field; consider adding defensive handling (e.g., try/except or type/shape checks) to fall back to an empty list rather than raising if the API response format changes or is unexpected.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `IssueHandler.add_issue_links`, existing links are de-duplicated solely by issue key, so you will skip creating a link with a different relation type to an already-linked issue; if multiple relations to the same key should be allowed, consider tracking existing links by both relation and key.
- The per-key existence check in `add_issue_links` (`self.connection.issue(linked_key)`) can become expensive for configurations with many links; you may want to make this validation optional or narrow the exception handling to only skip on 404-like errors while surfacing other failures.
- In `ErrataTool.get_errata`, `fetch_jira_issues` is assumed to return a list of dicts with a `key` field; consider adding defensive handling (e.g., try/except or type/shape checks) to fall back to an empty list rather than raising if the API response format changes or is unexpected.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@kkaarreell kkaarreell force-pushed the ks_link_erratum_issues branch from e8a2b45 to 16089e1 Compare February 5, 2026 19:27
kkaarreell and others added 2 commits February 5, 2026 20:27
Fetch and store Jira issue keys associated with errata to enable better
tracking and linking between errata and their related Jira issues.

Changes:
- Add jira_issues attribute to Erratum model to store list of Jira keys
- Add fetch_jira_issues() method to ErrataTool to retrieve Jira issues
  from /advisory/{id}/jira_issues.json endpoint
- Update get_errata() to fetch and populate Jira issue keys for each erratum

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit implements comprehensive support for creating Jira issue links
defined in the issue-config `links` attribute, ensuring they work for both
newly created issues and re-used existing issues.

Key improvements:

1. Extract link creation into reusable method:
   - Created `add_issue_links()` method in IssueHandler to handle link
     creation with existence checking and validation
   - Refactored `create_issue()` to use the new method instead of inline code
   - Follows same optimization pattern as `link_erratum_issues()` (fetch all
     existing links once, use set for fast lookup)

2. Enable links for re-used issues:
   - Call `add_issue_links()` when existing issues are re-used (not just
     when created)
   - Ensures links defined in issue-config are always applied regardless of
     whether issue is new or re-used

3. Support Jinja2 template references to list variables:
   - Enhanced link rendering to handle both list of strings and template
     references to list variables using YAML parsing approach
   - Unified with recipes.py pattern: render template as string, then parse
     as YAML to get native types
   - Enables use cases like `"is related to": "{{ ERRATUM.jira_issues }}"` to
     dynamically link to all erratum Jira issues
   - Removed NativeEnvironment dependency in favor of consistent YAML parsing

4. Documentation:
   - Added comprehensive section on using links in issue configuration
   - Documented three supported formats: static lists, template strings, and
     template references to list variables
   - Updated example configuration to show dynamic linking

The implementation ensures link creation is idempotent (checks for existing
links and target issue existence), provides appropriate debug/info logging,
and works consistently across all issue lifecycle states.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@kkaarreell kkaarreell force-pushed the ks_link_erratum_issues branch from 16089e1 to cb14366 Compare February 5, 2026 19:28
@kkaarreell kkaarreell merged commit 89c3203 into main Feb 7, 2026
15 of 16 checks passed
@kkaarreell kkaarreell deleted the ks_link_erratum_issues branch February 7, 2026 07:21
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.

1 participant