Skip to content

Add --event-filter option for event/artifact level filtering#325

Merged
kkaarreell merged 1 commit into
mainfrom
ks_event_filter
Mar 2, 2026
Merged

Add --event-filter option for event/artifact level filtering#325
kkaarreell merged 1 commit into
mainfrom
ks_event_filter

Conversation

@kkaarreell

@kkaarreell kkaarreell commented Feb 27, 2026

Copy link
Copy Markdown
Collaborator

Implement event/artifact filtering using object.attribute=regex format to filter jobs by compose.id, erratum.id, erratum.release, or rog.id. The filter applies across all subcommands (event, jira, schedule, execute, report, summarize, list) and works with --copy-state-dir and --extract-state-dir options.

Key changes:

  • Add EventFilter class to newa/models/settings.py
  • Create newa/cli/event_helpers.py with parse_event_filter() and should_filter_by_event() helper functions
  • Update event subcommand to filter before saving YAML files
  • Add filter_events parameter to load_artifact_jobs() and related methods
  • Fix circular import in newa/cli/init.py by importing CLIContext directly from newa.models.settings
  • Add comprehensive documentation to README.md
  • Add 11 new unit tests covering filter parsing and matching

🤖 Generated with Claude Code

Summary by Sourcery

Introduce a configurable event/artifact-level filter that limits which jobs are created and processed based on event attributes, and apply it consistently across CLI commands and state-dir operations.

New Features:

  • Add an --event-filter CLI option that accepts object.attribute=regex expressions to filter jobs by compose.id, erratum.id, erratum.release, or rog.id across all subcommands.
  • Introduce an EventFilter model and helper functions to parse and evaluate event/artifact filters against jobs.

Enhancements:

  • Extend CLIContext and job loading helpers so event filtering is applied when creating, copying, listing, and processing jobs from state directories.
  • Update event, jira, and list commands to respect event-level filters when generating or iterating over artifact jobs.
  • Improve YAML file filtering logic to consider event filters alongside existing action and issue filters.
  • Resolve a circular import in the CLI package by importing CLIContext directly from the settings module.

Documentation:

  • Document the new --event-filter option in the README, including supported filter forms, behavior across subcommands, and usage examples.

Tests:

  • Add unit tests covering event filter parsing, validation error cases, and matching behavior for compose, erratum (id and release), and RoG artifacts, as well as non-matching and missing-artifact scenarios.

@kkaarreell kkaarreell self-assigned this Feb 27, 2026
@sourcery-ai

sourcery-ai Bot commented Feb 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new --event-filter CLI option and supporting EventFilter model/helpers to enable event/artifact-level regex filtering across YAML file selection and job-loading pipelines, and wires this filtering into event, jira, and list subcommands while updating docs and tests and fixing a CLI circular import.

Sequence diagram for the new --event-filter processing across CLI main and event command

sequenceDiagram
    actor User
    participant CLI_main as main
    participant EventHelpers as event_helpers
    participant CLIContext as CLIContext
    participant EventCmd as cmd_event
    participant FS as StateDir
    participant ArtifactJob as ArtifactJob

    User->>CLI_main: invoke newa --event-filter "erratum.release=RHEL-9.2.*" event

    CLI_main->>EventHelpers: parse_event_filter("erratum.release=RHEL-9.2.*")
    EventHelpers-->>CLI_main: EventFilter

    CLI_main->>CLIContext: create(ctx, event_filter_pattern=EventFilter, action_id_filter_pattern, issue_id_filter_pattern,...)
    CLI_main-->>User: command dispatch to subcommand event

    User->>EventCmd: event subcommand
    EventCmd->>CLIContext: copy_events_from_previous_statedir()

    note over CLIContext,FS: When copying from previous state dir
    CLIContext->>FS: iterate YAML files with prefix event_
    CLIContext->>CLIContext: load_artifact_jobs(filter_events=true)

    loop for each YAML file
        CLIContext->>CLIContext: load_artifact_job(filepath)
        CLIContext->>ArtifactJob: construct from YAML
        CLIContext->>CLIContext: should_filter_job(ArtifactJob)
        alt event_filter_pattern set
            CLIContext->>EventHelpers: should_filter_by_event(EventFilter, ArtifactJob, logger)
            EventHelpers-->>CLIContext: true or false
            alt should_filter_by_event returns true
                CLIContext-->>CLIContext: skip ArtifactJob
            else should_filter_by_event returns false
                CLIContext-->>EventCmd: yield ArtifactJob
            end
        else no event_filter_pattern
            CLIContext-->>EventCmd: yield ArtifactJob
        end
    end

    note over EventCmd,FS: When creating new ArtifactJob instances
    EventCmd->>ArtifactJob: create for each compose/erratum/rog
    EventCmd->>CLIContext: should_filter_job(ArtifactJob)
    CLIContext->>EventHelpers: should_filter_by_event(EventFilter, ArtifactJob, logger)
    EventHelpers-->>CLIContext: true or false
    alt should_filter_by_event returns false
        CLIContext-->>EventCmd: process ArtifactJob and save YAML
    else
        CLIContext-->>EventCmd: skip saving YAML
    end
Loading

Updated class diagram for CLIContext, EventFilter, and event_helpers

classDiagram
    class Settings {
    }

    class EventFilter {
        +str object_type
        +str attribute
        +Pattern~str~ pattern
    }

    class CLIContext {
        +Settings settings
        +logging.Logger logger
        +Path state_dirpath
        +bool force
        +Pattern~str~ action_id_filter_pattern
        +Pattern~str~ issue_id_filter_pattern
        +EventFilter event_filter_pattern
        +JiraConnection jira_connection
        +bool should_filter_job(job ArtifactJob) bool
        +Iterator~ArtifactJob~ load_artifact_jobs(filename_prefix str, filter_events bool) Iterator
        +Iterator~JiraJob~ load_jira_jobs() Iterator
        +Iterator~ScheduleJob~ load_schedule_jobs() Iterator
        +Iterator~ExecuteJob~ load_execute_jobs() Iterator
    }

    class ArtifactJob {
        +str id
        +Compose compose
        +Erratum erratum
        +Rog rog
    }

    class JiraJob {
        +str id
        +Compose compose
        +Erratum erratum
        +Rog rog
    }

    class ScheduleJob {
        +str id
        +Compose compose
        +Erratum erratum
        +Rog rog
    }

    class ExecuteJob {
        +str id
        +Compose compose
        +Erratum erratum
        +Rog rog
    }

    class Compose {
        +str id
    }

    class Erratum {
        +str id
        +str release
    }

    class Rog {
        +str id
    }

    class event_helpers {
        <<utility>>
        +parse_event_filter(event_filter str) EventFilter
        +should_filter_by_event(event_filter EventFilter, job ArtifactJob, logger Logger, log_message bool) bool
    }

    Settings <|-- CLIContext
    EventFilter o-- CLIContext : event_filter_pattern

    ArtifactJob <|-- JiraJob
    ArtifactJob <|-- ScheduleJob
    ArtifactJob <|-- ExecuteJob

    Compose o-- ArtifactJob
    Erratum o-- ArtifactJob
    Rog o-- ArtifactJob

    event_helpers ..> EventFilter
    CLIContext ..> event_helpers
    CLIContext ..> ArtifactJob
    CLIContext ..> JiraJob
    CLIContext ..> ScheduleJob
    CLIContext ..> ExecuteJob
Loading

File-Level Changes

Change Details Files
Introduce an EventFilter model and shared helpers to parse and apply object.attribute=regex event filters against jobs.
  • Add EventFilter dataclass to settings to capture object_type, attribute, and compiled regex pattern.
  • Implement parse_event_filter() to validate object/attribute combinations, compile the regex, and raise ClickException on invalid input.
  • Implement should_filter_by_event() to derive the relevant artifact attribute from a job and decide whether to skip it, with configurable logging verbosity.
  • Add unit tests covering parsing success/validation failures and matching behavior for compose, erratum (id/release), and rog artifacts.
newa/models/settings.py
newa/cli/event_helpers.py
tests/unit/test_filters.py
Thread the event filter through CLI context, YAML filtering, and job-loading/iteration so downstream commands respect the configured filter.
  • Extend CLIContext with event_filter_pattern, a should_filter_job() helper, and an optional filter_events flag in load_artifact_jobs() that skips non-matching jobs.
  • Update _should_filter_yaml_file() to accept an EventFilter, construct an ArtifactJob from parsed YAML, and reuse should_filter_by_event() to decide file deletion/skipping.
  • Wire parsing of the --event-filter option in main(), construct an EventFilter object when provided, and pass it into CLIContext and YAML cleanup/copy loops.
  • Ensure jira, schedule, execute, and list job iterators all call should_filter_job() so non-matching jobs are consistently skipped.
newa/models/settings.py
newa/cli/main.py
Integrate event filtering into specific subcommands and fix CLI package imports.
  • Change event command helpers (copy_events_from_previous_statedir, process_event_errata/composes/rog_urls/jira_keys) to apply ctx.should_filter_job() before saving ArtifactJob YAMLs.
  • Update jira and list commands to load artifact jobs with filter_events=True so the new filter is applied early.
  • Resolve a circular import in newa.cli by importing CLIContext from newa.models.settings instead of newa.
  • Document the new --event-filter option, supported attributes, behavior across subcommands, and usage examples in README.md.
newa/cli/commands/event_cmd.py
newa/cli/commands/jira_cmd.py
newa/cli/commands/list_cmd.py
newa/cli/__init__.py
README.md

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

  • In _should_filter_yaml_file you currently parse the YAML once with yaml.safe_load and then again via ArtifactJob.from_yaml_file for the event filter; consider reusing the already loaded data (or using ArtifactJob.from_dict) to avoid double I/O and parsing on every file.
  • The event-filter application logic is duplicated in several places (event command handlers, load_*_jobs, _should_filter_yaml_file); extracting a small helper on CLIContext (e.g. ctx.should_filter_job(job)) would reduce repetition and make changes to filter semantics easier to apply consistently.
  • The should_filter_by_event helper is typed to accept an ArtifactJob, but it is also used on Jira/Schedule/Execute jobs in load_*_jobs; if those types are intentionally supported, updating the type hints (or introducing a common protocol/interface) would make this clearer and improve static checking.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_should_filter_yaml_file` you currently parse the YAML once with `yaml.safe_load` and then again via `ArtifactJob.from_yaml_file` for the event filter; consider reusing the already loaded data (or using `ArtifactJob.from_dict`) to avoid double I/O and parsing on every file.
- The event-filter application logic is duplicated in several places (event command handlers, `load_*_jobs`, `_should_filter_yaml_file`); extracting a small helper on `CLIContext` (e.g. `ctx.should_filter_job(job)`) would reduce repetition and make changes to filter semantics easier to apply consistently.
- The `should_filter_by_event` helper is typed to accept an `ArtifactJob`, but it is also used on Jira/Schedule/Execute jobs in `load_*_jobs`; if those types are intentionally supported, updating the type hints (or introducing a common protocol/interface) would make this clearer and improve static checking.

## Individual Comments

### Comment 1
<location path="newa/models/settings.py" line_range="251-260" />
<code_context>

     def load_artifact_jobs(
             self,
-            filename_prefix: str = EVENT_FILE_PREFIX) -> Iterator['ArtifactJob']:
+            filename_prefix: str = EVENT_FILE_PREFIX,
+            filter_events: bool = False) -> Iterator['ArtifactJob']:
         for child in self.state_dirpath.iterdir():
             if not child.name.startswith(filename_prefix):
                 continue

-            yield self.load_artifact_job(child.resolve())
+            job = self.load_artifact_job(child.resolve())
+            if filter_events and self.event_filter_pattern:
+                from newa.cli.event_helpers import should_filter_by_event
+                if should_filter_by_event(self.event_filter_pattern, job, self.logger):
+                    continue
+            yield job
</code_context>
<issue_to_address>
**suggestion:** Consider using debug-level logging when filtering artifacts to avoid log noise.

When `filter_events=True` and `event_filter_pattern` is set, this calls `should_filter_by_event` with the default `log_message=True`, producing an info log for every skipped job. For large state directories this can flood logs, whereas `_should_filter_yaml_file` passes `log_message=False` to keep these at debug level. Consider passing `log_message=False` here (and at similar internal filtering call sites) for consistency and reduced noise.

Suggested implementation:

```python
            job = self.load_artifact_job(child.resolve())
            if filter_events and self.event_filter_pattern:
                from newa.cli.event_helpers import should_filter_by_event
                if should_filter_by_event(self.event_filter_pattern, job, self.logger, log_message=False):
                    continue
            yield job

```

```python
                if self.event_filter_pattern:
                    from newa.cli.event_helpers import should_filter_by_event
                    if should_filter_by_event(self.event_filter_pattern, job, self.logger, log_message=False):
                        continue

```

Step-by-step:
1. In `load_artifact_jobs`, keep the existing filtering logic but pass `log_message=False` to `should_filter_by_event` so that skipped jobs are only logged at debug level.
2. In the Jira job loading logic shown, also pass `log_message=False` to `should_filter_by_event` for consistent, low-noise logging across internal filtering paths.
3. Search the rest of the codebase for other internal calls to `should_filter_by_event` and, where appropriate (i.e., non-user-facing bulk filtering), update them to pass `log_message=False` as well.
</issue_to_address>

### Comment 2
<location path="tests/unit/test_filters.py" line_range="213-219" />
<code_context>
+        assert filter_obj.attribute == 'id'
+        assert filter_obj.pattern.pattern == 'https://.*'
+
+    def test_parse_invalid_format(self):
+        """Test parsing invalid filter format."""
+        import click
+
+        from newa.cli.event_helpers import parse_event_filter
+
+        with pytest.raises(click.ClickException, match='Invalid --event-filter format'):
+            parse_event_filter('invalid_format')
+
</code_context>
<issue_to_address>
**issue (testing):** Add a test for invalid regular expression patterns in --event-filter

`parse_event_filter` also raises a `ClickException` when the regex fails to compile. Please add a test (e.g., for `compose.id=[invalid`) that covers this branch and asserts the exception message, so invalid regex patterns are verified as well.
</issue_to_address>

### Comment 3
<location path="tests/unit/test_filters.py" line_range="274-255" />
<code_context>
+    def test_should_filter_erratum_release_matching(self, mock_logger):
</code_context>
<issue_to_address>
**issue (testing):** Missing tests for non-matching and missing-value cases in should_filter_by_event for erratum filters

Please also add tests for:
- A non-matching `erratum.release` (e.g. `test_should_filter_erratum_release_not_matching`) to confirm it is filtered out.
- An erratum where the filter field (e.g. `release` or `id`) is empty or `None`, asserting it is filtered and the "has no value" path is covered.

This will verify `should_filter_by_event` behavior when artifacts are present but incomplete.
</issue_to_address>

### Comment 4
<location path="tests/unit/test_filters.py" line_range="204-211" />
<code_context>
+        assert filter_obj.attribute == 'release'
+        assert filter_obj.pattern.pattern == 'RHEL-9.5'
+
+    def test_parse_rog_id_filter(self):
+        """Test parsing rog.id filter."""
+        from newa.cli.event_helpers import parse_event_filter
+
+        filter_obj = parse_event_filter('rog.id=https://.*')
+        assert filter_obj.object_type == 'rog'
+        assert filter_obj.attribute == 'id'
+        assert filter_obj.pattern.pattern == 'https://.*'
+
+    def test_parse_invalid_format(self):
</code_context>
<issue_to_address>
**issue (testing):** Add tests for should_filter_by_event behavior with rog filters

The `rog` branch of `should_filter_by_event` isn’t exercised by tests yet. Please add cases where a `rog` job’s `rog.id` matches the filter (expect `False`) and where it doesn’t match (expect `True`) to verify both the branch behavior and attribute extraction.
</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.

Comment thread newa/models/settings.py Outdated
Comment thread tests/unit/test_filters.py
Comment thread tests/unit/test_filters.py
Comment thread tests/unit/test_filters.py
@kkaarreell

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


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

Copy link
Copy Markdown
Collaborator Author

/packit test

2 similar comments
@kkaarreell

Copy link
Copy Markdown
Collaborator Author

/packit test

@kkaarreell

Copy link
Copy Markdown
Collaborator Author

/packit test

Implement event/artifact filtering using object.attribute=regex format to
filter jobs by compose.id, erratum.id, erratum.release, or rog.id. The
filter applies across all subcommands (event, jira, schedule, execute,
report, summarize, list) and works with --copy-state-dir and
--extract-state-dir options.

Key changes:
- Add EventFilter class to newa/models/settings.py
- Create newa/cli/event_helpers.py with parse_event_filter() and
  should_filter_by_event() helper functions
- Update event subcommand to filter before saving YAML files
- Add filter_events parameter to load_artifact_jobs() and related methods
- Fix circular import in newa/cli/__init__.py by importing CLIContext
  directly from newa.models.settings
- Add comprehensive documentation to README.md
- Add 11 new unit tests covering filter parsing and matching

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

Co-Authored-By: Claude <noreply@anthropic.com>
@kkaarreell kkaarreell merged commit fef6a5e into main Mar 2, 2026
18 checks passed
@kkaarreell kkaarreell deleted the ks_event_filter branch March 2, 2026 09:10
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