Skip to content

Fixes #37729 - Add version option for deb filter rules #11454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

hstct
Copy link
Contributor

@hstct hstct commented Jul 30, 2025

What are the changes introduced in this pull request?

  1. Reuses the deb_version_cmp functions to fix the version search and version search ranges for Debian content.
  2. Fixes the the autocomplete in the debs_controller
  3. Adds UI changes for the Debian Content View Filter Rules to be on par with the RPM implementation.
  4. Adds tests for the ruby implementation (autocomplete and filter rules controller)

Considerations taken when implementing this change?

What are the testing steps for this pull request?

  1. Create a Debian Product and sync content
  2. Create a new ContentView and add the debian repository
  3. Create new debian filter rules that include (or exclude) certain packages and their versions.
  • Supports equals, greater than, lesser than, or a range.

Summary by Sourcery

Introduce version-based filtering in Debian package filter rules, align the DEB filter UI with RPM conventions, and ensure backend support for version comparisons.

New Features:

  • Add version comparator dropdown with equal, greater than, less than, range options in the Add/Edit DEB filter rule modal
  • Include version, minimum version, and maximum version inputs and a help popover explaining version policy
  • Enable name and architecture autocomplete in DEB filter rules using SearchText

Bug Fixes:

  • Correct debs_controller autocomplete logic for name and arch searches, including blank-term handling and repository filtering

Enhancements:

  • Implement Deb model scopes using deb_version_cmp for equal and range version searches
  • Apply the new version scopes in ContentViewDebFilter query logic
  • Dispatch content view publish flag when rules change

Tests:

  • Add JavaScript tests for DEB filter rule UI (listing, searching, adding, editing, removing)
  • Add controller tests for debs autocomplete and ContentViewDebFilterRules API
  • Add model tests for Deb version comparison and search scopes

Copy link

sourcery-ai bot commented Jul 30, 2025

Reviewer's Guide

Enable version-based filtering in Debian content view rules by introducing comparator UI in the frontend modal, reusing deb_version_cmp SQL scopes in models, refining the debs API for autocomplete, and expanding test coverage across both frontend and backend.

Sequence diagram for DEB filter rule creation with version comparator

sequenceDiagram
  actor User
  participant Modal as AddEditDebPackageRuleModal
  participant API as DebsController
  participant Model as ContentViewDebFilter

  User->>Modal: Open Add DEB rule modal
  Modal->>API: Autocomplete package name/arch (as user types)
  API-->>Modal: Return autocomplete suggestions
  User->>Modal: Selects version comparator and enters version(s)
  User->>Modal: Submit rule
  Modal->>Model: addCVFilterRule (with name, arch, version/min/max)
  Model->>Model: Apply search_version_equal or search_version_range
  Model-->>Modal: Rule saved
  Modal->>User: Show updated rules and trigger publish-needed event
Loading

Entity relationship diagram for ContentViewDebFilter and Deb filter rules

erDiagram
  CONTENT_VIEW_DEB_FILTER ||--o{ CONTENT_VIEW_DEB_FILTER_RULE : has
  CONTENT_VIEW_DEB_FILTER_RULE {
    integer id
    string name
    string architecture
    string version
    string min_version
    string max_version
  }
  CONTENT_VIEW_DEB_FILTER {
    integer id
    string name
  }
Loading

File-Level Changes

Change Details Files
Revamped front-end DEB filter rule modal to support version comparators and autocompletion
  • Added a version comparator dropdown with options: All, Equal, Greater, Less, Range
  • Conditionally render version, min_version, and max_version input fields based on comparator
  • Replaced TextInput fields with SearchText for package name and architecture auto-complete
  • Added a help popover and aligned UI labels with the RPM implementation
  • Dispatched a CONTENT_VIEW_NEEDS_PUBLISH event after adding or editing rules
webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js
Refactored debs_controller autocomplete to handle blank terms and correct column lookups
  • Unified column selection logic using a case expression and return empty array if no match
  • Switched to wildcard ILIKE matching only when term is present
  • Adjusted auto_complete_arch to pass 'arch' instead of 'architecture'
  • Ensured repository filtering also works without explicit repoids
app/controllers/katello/api/v2/debs_controller.rb
Replaced custom package filter logic with SQL deb_version_cmp scopes for version comparisons
  • Introduced scope.search_version_equal and scope.search_version_range using deb_version_cmp
  • Removed legacy PackageFilter utility methods
  • Updated ContentViewDebFilter.query_debs_from_collection to apply new scopes for version, min_version, and max_version
app/models/katello/deb.rb
app/models/katello/content_view_deb_filter.rb
Expanded test suite and fixtures to cover Debian version filtering across frontend, backend controllers, and models
  • Added controller tests for debs_controller auto-completion and blank-term handling
  • Added content_view_deb_filter_rules API tests covering CRUD operations and permission checks
  • Added Deb model tests for version comparison semantics, including epoch handling and ranges
  • Created React integration tests for ContentView deb filter details and rule modals
  • Introduced fixtures for content view deb filters, filter rules, and frontend JSON fixtures
test/controllers/api/v2/debs_controller_test.rb
test/controllers/api/v2/content_view_deb_filter_rules_controller_test.rb
test/models/deb_test.rb
test/fixtures/models/katello_content_view_filters.yml
test/fixtures/models/katello_content_view_deb_filter_rules.yml
webpack/scenes/ContentViews/Details/Filters/__tests__/CVDebFilterContent.test.js
webpack/scenes/ContentViews/Details/Filters/__tests__/cvDebFilterDetail.fixtures.json
webpack/scenes/ContentViews/Details/Filters/__tests__/cvDebFilterRules.fixtures.json
webpack/scenes/ContentViews/Details/Filters/__tests__/emptyCVDebFilterRules.fixtures.json

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

@hstct hstct force-pushed the content_view_filter_deb branch 2 times, most recently from ee56c5f to 2d91bb6 Compare July 31, 2025 07:19
@hstct
Copy link
Contributor Author

hstct commented Jul 31, 2025

Forgot to add the webpack tests

@hstct hstct marked this pull request as ready for review July 31, 2025 07:20
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 @hstct - I've reviewed your changes - here's some feedback:

  • In DebVersionSearchTest, a couple of methods are missing the "test_" prefix (e.g. search_version_less_than), so they never run under Minitest—rename them to start with "test_".
  • The VersionModifiers/FormSelectOption mapping currently iterates over values and then uses the value as a key, which may misalign label/value pairs; switch to iterating over Object.entries so you can explicitly set option value and label.
  • The SearchText fields use defaultValue which makes them uncontrolled after mount—consider passing value and onChange props to keep them as controlled components and keep the UI in sync with state.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In DebVersionSearchTest, a couple of methods are missing the "test_" prefix (e.g. search_version_less_than), so they never run under Minitest—rename them to start with "test_".
- The VersionModifiers/FormSelectOption mapping currently iterates over values and then uses the value as a key, which may misalign label/value pairs; switch to iterating over Object.entries so you can explicitly set option value and label.
- The SearchText fields use defaultValue which makes them uncontrolled after mount—consider passing value and onChange props to keep them as controlled components and keep the UI in sync with state.

## Individual Comments

### Comment 1
<location> `webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js:60` </location>
<code_context>
+  const [version, setVersion] = useState(editingVersion || '');
+  const [minVersion, setMinVersion] = useState(editingMinVersion || '');
+  const [maxVersion, setMaxVersion] = useState(editingMaxVersion || '');
+  const [versionComparator, setVersionComparator] = useState(versionText(selectedFilterRuleData));
   const [saving, setSaving] = useState(false);
   const dispatch = useDispatch();
</code_context>

<issue_to_address>
The initial value for versionComparator may not match the expected type.

Since versionComparator stores a localized string, any changes in translations or usage of these keys elsewhere could cause subtle bugs. It's safer to store a non-localized key in state and map to the localized string only for display purposes.

Suggested implementation:

```javascript
  const [version, setVersion] = useState(editingVersion || '');
  const [minVersion, setMinVersion] = useState(editingMinVersion || '');
  const [maxVersion, setMaxVersion] = useState(editingMaxVersion || '');
  // Store the key (e.g., 'Equal to', 'Greater than', etc.) instead of the localized string
  const [versionComparator, setVersionComparator] = useState(getVersionComparatorKey(selectedFilterRuleData));
  const [saving, setSaving] = useState(false);

```

```javascript
  const submitDisabled = !name || name.length === 0;

  // Compare against the key, not the localized string
  const showVersion = versionComparator === VersionModifiers.Equal;
  const showMinVersion =
    versionComparator === VersionModifiers.Greater ||
    versionComparator === VersionModifiers.Range;
  const showMaxVersion =
    versionComparator === VersionModifiers.Less ||

```

1. You will need to implement the `getVersionComparatorKey(selectedFilterRuleData)` function, which should extract the correct key (e.g., 'Equal', 'Greater', 'Less', 'Range', etc.) from `selectedFilterRuleData`.
2. Ensure that wherever you display the comparator in the UI, you map the key to the localized string using your `versionText` function or similar.
3. If `VersionModifiers` is an object with keys like `Equal`, `Greater`, etc., make sure to use those keys consistently throughout the file.
4. Update any code that sets or reads `versionComparator` to use the key, not the localized string.
</issue_to_address>

### Comment 2
<location> `webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js:65` </location>
<code_context>
   const dispatch = useDispatch();
   const status = useSelector(state => selectCreateFilterRuleStatus(state));

   const submitDisabled = !name || name.length === 0;

+  const showVersion = versionComparator === VersionModifiers['Equal to'];
</code_context>

<issue_to_address>
submitDisabled only checks for name, not for required version fields.

Extend submitDisabled to also check that required version fields are filled when the selected comparator requires them. This will prevent submission of incomplete rules.
</issue_to_address>

### Comment 3
<location> `webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js:140` </location>
<code_context>
       isOpen
       onClose={onClose}
       appendTo={document.body}
+      help={
+        <Popover
+          headerContent={__('Help')}
+          bodyContent={__('Filter rules may include or exclude specific Debian packages' +
+        'by name, architecture, and version. Version comparisons follow Debian policy' +
+        '(epoch:upstream-revision; "~" sorts before empty). Leave all version fields' +
+        'blank to match all version.')}
+        >
+          <Button variant="plain" aria-label="Help" ouiaId="deb-filter-rule-modal-help">
+            <HelpIcon />
+          </Button>
+        </Popover>
+      }
     >
</code_context>

<issue_to_address>
The help popover text contains a minor typo: 'blank to match all version.'

It should read 'all versions' instead of 'all version.'
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        'blank to match all version.')}
=======
        'blank to match all versions.')}
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js:192` </location>
<code_context>
+            name="version_comparator"
+            aria-label="version_comparator"
+          >
+            {Object.values(VersionModifiers).map(v => (
+              <FormSelectOption key={v} value={v} label={VersionModifiers[v]} />
+            ))}
+          </FormSelect>
</code_context>

<issue_to_address>
Using localized strings as keys in FormSelectOption may cause issues with translation changes.

Use a stable, non-localized key and value for FormSelectOption, reserving the localized string for the label to prevent issues with translation changes or duplicate labels.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
            {Object.values(VersionModifiers).map(v => (
              <FormSelectOption key={v} value={v} label={VersionModifiers[v]} />
            ))}
=======
            {Object.entries(VersionModifiers).map(([modifierKey, localizedLabel]) => (
              <FormSelectOption key={modifierKey} value={modifierKey} label={localizedLabel} />
            ))}
>>>>>>> REPLACE

</suggested_fix>

### Comment 5
<location> `test/models/deb_test.rb:53` </location>
<code_context>
+      refute_includes result, @deb_one
+    end
+
+    def search_version_less_than
+      result = Deb.in_repositories(@repo).search_for('version < 1.1')
+      assert_includes result, @deb_one
+      refute_includes result, @deb_one_v2
+    end
+
</code_context>

<issue_to_address>
Test method 'search_version_less_than' is missing 'test_' prefix.

Please rename the method to 'test_search_version_less_than' so it is recognized and executed as a test.
</issue_to_address>

### Comment 6
<location> `test/models/deb_test.rb:59` </location>
<code_context>
+      refute_includes result, @deb_one_v2
+    end
+
+    def search_version_equal
+      result = Deb.in_repositories(@repo).search_for('version = 1.0')
+      assert_includes result, @deb_one
+      refute_includes result, @deb_one_v2
+    end
+
</code_context>

<issue_to_address>
Test method 'search_version_equal' is missing 'test_' prefix.

Please rename the method to 'test_search_version_equal' so it is recognized and executed as a test.
</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.

const [version, setVersion] = useState(editingVersion || '');
const [minVersion, setMinVersion] = useState(editingMinVersion || '');
const [maxVersion, setMaxVersion] = useState(editingMaxVersion || '');
const [versionComparator, setVersionComparator] = useState(versionText(selectedFilterRuleData));
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): The initial value for versionComparator may not match the expected type.

Since versionComparator stores a localized string, any changes in translations or usage of these keys elsewhere could cause subtle bugs. It's safer to store a non-localized key in state and map to the localized string only for display purposes.

Suggested implementation:

  const [version, setVersion] = useState(editingVersion || '');
  const [minVersion, setMinVersion] = useState(editingMinVersion || '');
  const [maxVersion, setMaxVersion] = useState(editingMaxVersion || '');
  // Store the key (e.g., 'Equal to', 'Greater than', etc.) instead of the localized string
  const [versionComparator, setVersionComparator] = useState(getVersionComparatorKey(selectedFilterRuleData));
  const [saving, setSaving] = useState(false);
  const submitDisabled = !name || name.length === 0;

  // Compare against the key, not the localized string
  const showVersion = versionComparator === VersionModifiers.Equal;
  const showMinVersion =
    versionComparator === VersionModifiers.Greater ||
    versionComparator === VersionModifiers.Range;
  const showMaxVersion =
    versionComparator === VersionModifiers.Less ||
  1. You will need to implement the getVersionComparatorKey(selectedFilterRuleData) function, which should extract the correct key (e.g., 'Equal', 'Greater', 'Less', 'Range', etc.) from selectedFilterRuleData.
  2. Ensure that wherever you display the comparator in the UI, you map the key to the localized string using your versionText function or similar.
  3. If VersionModifiers is an object with keys like Equal, Greater, etc., make sure to use those keys consistently throughout the file.
  4. Update any code that sets or reads versionComparator to use the key, not the localized string.

Comment on lines +192 to +194
{Object.values(VersionModifiers).map(v => (
<FormSelectOption key={v} value={v} label={VersionModifiers[v]} />
))}
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Using localized strings as keys in FormSelectOption may cause issues with translation changes.

Use a stable, non-localized key and value for FormSelectOption, reserving the localized string for the label to prevent issues with translation changes or duplicate labels.

Suggested change
{Object.values(VersionModifiers).map(v => (
<FormSelectOption key={v} value={v} label={VersionModifiers[v]} />
))}
{Object.entries(VersionModifiers).map(([modifierKey, localizedLabel]) => (
<FormSelectOption key={modifierKey} value={modifierKey} label={localizedLabel} />
))}

Co-Authored-by: Markus Bucher <[email protected]>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@m-bucher m-bucher force-pushed the content_view_filter_deb branch from 709d6e1 to caed97d Compare July 31, 2025 10:27
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.

2 participants