Skip to content

fix(RHINENG-24563): Implement client-side sorting for RBAC v2 (updated, created, name) since RBAC API doesn't support order_how#3791

Closed
thearifismail wants to merge 4 commits intomasterfrom
RHINENG-24563
Closed

fix(RHINENG-24563): Implement client-side sorting for RBAC v2 (updated, created, name) since RBAC API doesn't support order_how#3791
thearifismail wants to merge 4 commits intomasterfrom
RHINENG-24563

Conversation

@thearifismail
Copy link
Copy Markdown
Contributor

@thearifismail thearifismail commented Mar 19, 2026

Overview

This PR is being created to address RHINENG-24563.
This PR fixes the order_how issue which has been a supported case because host-inventory used to pull groups data from its database using SQL queries. Now RBAC V2 does not support sorting by order_how.

@computercamplove support "order_how" is not trivial. What makes me uncomfortable is the size of the get_group_list() function, which was already too long. I thought I had created a JIRA card to refactor this function in future.

If possible I would like to fix this issue later, what do you think?

PR Checklist

  • Keep PR title short, ideally under 72 characters
  • Descriptive comments provided in complex code blocks
  • Include raw query examples in the PR description, if adding/modifying SQL query
  • Tests: validate optimal/expected output
  • Tests: validate exceptions and failure scenarios
  • Tests: edge cases
  • Recovers or fails gracefully during potential resource outages (e.g. DB, Kafka)
  • Uses type hinting, if convenient
  • Documentation, if this PR changes the way other services interact with host inventory
  • Links to related PRs

Secure Coding Practices Documentation Reference

You can find documentation on this checklist here.

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

Summary by Sourcery

Implement client-side sorting for RBAC v2 group listings when ordering by updated, created, name, or type, with smart defaults to mirror previous RBAC v1 behavior.

Bug Fixes:

  • Ensure groups endpoint applies client-side sorting for RBAC v2 when order_how is unsupported, including smart default DESC ordering for updated/created and ASC for name/type.
  • Prevent unsortable responses by rejecting client-side sorting when the organization exceeds the maximum number of groups that can be safely sorted in memory.

Enhancements:

  • Extend RBAC workspace fetching and serialization flow to support client-side sorting and stable secondary ordering by name while remaining forward-compatible with potential future RBAC order_how support.

Tests:

  • Update RBAC v2 ordering tests to assert that get_rbac_workspaces is invoked without order_by/order_how for client-side sorting.
  • Add an end-to-end test validating smart default DESC ordering for updated when using RBAC v2 client-side sorting.

@github-actions
Copy link
Copy Markdown
Contributor

SC Environment Impact Assessment

Overall Impact: 🟢 LOW

View full report

Summary

  • Total Issues: 1
  • 🟢 Low: 1

Detailed Findings

🟢 LOW Impact

Feature flag change detected

  • File: tests/test_api_groups_get.py
  • Category: feature_flags
  • Details:
    • Found feature flag in tests/test_api_groups_get.py at line 726
    • Found bypass_ in tests/test_api_groups_get.py at line 728
  • Recommendation: Verify feature flags are properly configured for SC Environment. Test bypass options for services not available in SC Environment.

Required Actions

  • Review all findings above
  • Verify SC Environment compatibility for all detected changes
  • Update deployment documentation if needed
  • Coordinate with ROSA Core team or deployment timeline

This assessment was automatically generated. Please review carefully and consult with the ROSA Core team for critical/high impact changes.

Copy link
Copy Markdown
Contributor

@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 3 issues, and left some high level feedback:

  • The client-side sorting branch maps order_by == "type" to the "ungrouped" boolean field, which may be surprising for callers expecting sorting by the RBAC workspace type; consider either sorting by the actual type value or adding a clear comment explaining that a request for type intentionally sorts by ungrouped instead.
  • In the client-side sorting path you perform two consecutive sort() calls to get a secondary key on name; this could be simplified and made more explicit by using a single sort with a composite key (e.g., key=lambda g: (g.get(sort_field), g.get("name", ""))) instead of relying on sort stability.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The client-side sorting branch maps `order_by == "type"` to the `"ungrouped"` boolean field, which may be surprising for callers expecting sorting by the RBAC workspace `type`; consider either sorting by the actual type value or adding a clear comment explaining that a request for `type` intentionally sorts by `ungrouped` instead.
- In the client-side sorting path you perform two consecutive `sort()` calls to get a secondary key on `name`; this could be simplified and made more explicit by using a single sort with a composite key (e.g., `key=lambda g: (g.get(sort_field), g.get("name", ""))`) instead of relying on sort stability.

## Individual Comments

### Comment 1
<location path="api/group.py" line_range="240-249" />
<code_context>
+                # Fetch all groups, sort in Python, then paginate
+
+                # Step 1: Fetch all groups from RBAC v2 (up to limit)
+                group_list, total = get_rbac_workspaces(
+                    name, 1, MAX_GROUPS_FOR_HOST_COUNT_SORTING, group_type, None, None
+                )
+
+                # Validate that we can sort all groups
+                if total > MAX_GROUPS_FOR_HOST_COUNT_SORTING:
+                    log_get_group_list_failed(logger)
+                    abort(
+                        400,
+                        f"Cannot sort by {order_by}: organization has {total} groups, which exceeds "
+                        f"the maximum of {MAX_GROUPS_FOR_HOST_COUNT_SORTING} groups that can be sorted. "
+                        f"Please use filters (name, group_type) to narrow your results.",
+                    )
+
</code_context>
<issue_to_address>
**suggestion:** Decouple the host-count-specific limit from the generic client-side sorting limit.

`MAX_GROUPS_FOR_HOST_COUNT_SORTING` now acts as a global cap for all client-side sorting (e.g., `updated`, `created`, `name`, `type`), not just host-count-related behavior. This couples a host-count-specific constant to general sorting. Consider introducing a generic limit (e.g., `MAX_GROUPS_FOR_CLIENT_SIDE_SORTING`) or renaming the existing constant to reflect its broader scope so host-count-specific changes don’t unintentionally affect other sorts.

Suggested implementation:

```python
                group_list, total = get_rbac_workspaces(
                    name, 1, MAX_GROUPS_FOR_CLIENT_SIDE_SORTING, group_type, None, None
                )

```

1. Define a new constant `MAX_GROUPS_FOR_CLIENT_SIDE_SORTING` in this module (or the shared config/constants module where `MAX_GROUPS_FOR_HOST_COUNT_SORTING` is defined) with the desired generic limit value.
2. Add/adjust any necessary imports so `MAX_GROUPS_FOR_CLIENT_SIDE_SORTING` is available in `api/group.py`.
3. Leave `MAX_GROUPS_FOR_HOST_COUNT_SORTING` in place for host-count-specific logic only, ensuring no other non-host-count code paths reference it.
</issue_to_address>

### Comment 2
<location path="tests/test_api_groups_get.py" line_range="709-718" />
<code_context>
+def test_get_groups_rbac_v2_smart_defaults_for_updated(mocker, api_get):
</code_context>
<issue_to_address>
**suggestion (testing):** Extend smart‑default ordering test to cover other sortable fields and explicit order_how overrides.

To better exercise the client-side sorting logic and the new `default_order_how` mapping, please also cover:

- `order_by=created` with no `order_how` (default DESC)
- `order_by=name` with no `order_how` (default ASC)
- `order_by=type` with no `order_how` (default ASC)
- Each of `updated`, `created`, `name`, `type` with `order_how=ASC` and `order_how=DESC` explicitly set, to confirm the explicit value overrides the smart default.

You can either parametrize this test or add sibling tests to achieve this coverage.

Suggested implementation:

```python
import pytest


@pytest.mark.parametrize(
    "order_by, order_how_param, expected_order_how",
    [
        # Smart defaults (no explicit order_how)
        ("updated", None, "DESC"),  # "Last modified" default
        ("created", None, "DESC"),  # "Created" default
        ("name", None, "ASC"),      # "Name" default
        ("type", None, "ASC"),      # "Type" default
        # Explicit overrides
        ("updated", "ASC", "ASC"),
        ("updated", "DESC", "DESC"),
        ("created", "ASC", "ASC"),
        ("created", "DESC", "DESC"),
        ("name", "ASC", "ASC"),
        ("name", "DESC", "DESC"),
        ("type", "ASC", "ASC"),
        ("type", "DESC", "DESC"),
    ],
)
def test_get_groups_rbac_v2_smart_defaults(
    mocker, api_get, order_by, order_how_param, expected_order_how
):
    """
    Test GET /groups with RBAC v2 applies client-side smart defaults and honors explicit
    `order_how` overrides for all sortable fields.

    Since RBAC v2 API doesn't support `order_how` parameter, host-inventory implements
    client-side sorting. When users click a sortable column without explicitly specifying
    `order_how`, they expect the "smart default" for that column. When `order_how` is
    explicitly provided, it must override the smart default.

    This test verifies that client-side sorting applies the correct smart defaults for
    `updated`, `created`, `name`, and `type`, and that explicit `order_how` values are
    honored, to match RBAC v1 behavior.
    """
    # Patch the RBAC v2 workspace-fetching function so we can inspect how it is called.
    # NOTE: keep this target consistent with the rest of this test module.
    get_rbac_workspaces = mocker.patch(
        "app.api.groups.get_rbac_workspaces", return_value=([], 0)
    )

    # Build query params based on the parametrized values.
    query_params = {"order_by": order_by}
    if order_how_param is not None:
        query_params["order_how"] = order_how_param

    # Act: call the groups endpoint using RBAC v2.
    response = api_get("/api/inventory/v1/groups", query_string=query_params)

    # Sanity check the request succeeded.
    assert response.status_code == 200

    # Verify that get_rbac_workspaces was called once with the expected order_by/order_how.
    get_rbac_workspaces.assert_called_once()
    call_args, _ = get_rbac_workspaces.call_args

    # For RBAC v2, the function is called with positional args; index 4 is order_by
    # and index 5 is order_how, as asserted in the other client-side sorting tests.
    assert call_args[4] == order_by
    assert call_args[5] == expected_order_how

```

To make this work with your existing test suite you should:

1. Adjust the patch target `"app.api.groups.get_rbac_workspaces"` to match whatever target you are *already* using in this file when asserting `call_args[0][4]` / `call_args[0][5]` for RBAC v2; reusing that same import path keeps tests consistent.
2. Confirm the URL `"/api/inventory/v1/groups"` matches the rest of this file; if your helper `api_get` already hard‑codes the base path or a different route, update the URL here to match.
3. If `pytest` is already imported at the top of the file, remove the duplicate `import pytest` in this block and keep just a single import.
4. If your existing `test_get_groups_rbac_v2_smart_defaults_for_updated` test contained additional setup/fixtures (e.g. enabling RBAC v2, seeding data, feature flags), move that setup into this parametrized test so behavior is identical for all parameter combinations.
</issue_to_address>

### Comment 3
<location path="tests/test_api_groups_get.py" line_range="768-773" />
<code_context>
+
+    assert_response_status(response_status, 200)
+
+    # Verify the results are in the expected order (DESC by default)
+    # If smart defaults weren't applied, results would be in ASC or random order
+    results = response_data["results"]
+    assert len(results) == 2
+    assert results[0]["name"] == "workspace-a"  # Newer first (DESC order)
+    assert results[1]["name"] == "workspace-b"  # Older second


</code_context>
<issue_to_address>
**suggestion (testing):** Add a test that validates deterministic secondary sorting by name for ties.

The current test only verifies primary ordering (newer vs older). Please add a case where multiple workspaces share the same `updated` (or `created`) value but have different `name`s, and assert that within that tie they are ordered lexicographically by `name`. This makes the secondary sort behavior explicit and guards against regressions if the sorting logic changes.

Suggested implementation:

```python
    query = "?order_by=updated"
    response_status, response_data = api_get(build_groups_url(query=query))

    assert_response_status(response_status, 200)

    # Verify deterministic secondary sorting by name when `updated` timestamps are equal.
    # We reconfigure the mock so that all returned workspaces share the same `updated` value
    # but have different `name`s; the API should order them lexicographically by `name`.
    tied_updated = "2024-01-01T00:00:00+00:00"
    mock_tied_workspaces = [
        {
            "id": "ws-1",
            "name": "workspace-a",
            "updated": tied_updated,
        },
        {
            "id": "ws-2",
            "name": "workspace-b",
            "updated": tied_updated,
        },
        {
            "id": "ws-3",
            "name": "workspace-c",
            "updated": tied_updated,
        },
    ]
    mock_get_rbac_workspaces.return_value = (mock_tied_workspaces, len(mock_tied_workspaces))

    response_status, response_data = api_get(build_groups_url(query=query))
    assert_response_status(response_status, 200)

    tied_results = response_data["results"]
    # Within the tie on `updated`, results must be ordered lexicographically by `name`
    assert [w["name"] for w in tied_results] == sorted([w["name"] for w in mock_tied_workspaces])

```

Depending on the rest of the test suite and the shape of `workspace` objects expected by the API code, you may need to:
1. Add any required fields (e.g. `created`, `display_name`, `host_count`, etc.) to each dict in `mock_tied_workspaces` so the endpoint logic does not fail when accessing them.
2. Ensure this block is inside the same test that verifies ordering by `updated` (the one whose `query` is `"?order_by=updated"`). If the function name implies it should only be testing part of this behavior, consider extracting the new assertions into a dedicated test instead.
</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.

@thearifismail thearifismail changed the title fix(RHINENG-24764): Implement client-side sorting for RBAC v2 (updated, created, name) since RBAC API doesn't support order_how fix(RHINENG-24563): Implement client-side sorting for RBAC v2 (updated, created, name) since RBAC API doesn't support order_how Mar 19, 2026
@thearifismail thearifismail marked this pull request as draft March 19, 2026 21:33
@github-actions
Copy link
Copy Markdown
Contributor

👋 Hi, this PR has been inactive for 7 days.
Please update it or let us know if you need help.
It will be automatically closed in 8 more days if there's no activity.

@github-actions github-actions bot added the stale label Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

This PR has been automatically closed due to 15 days of inactivity.
Feel free to reopen it if you'd like to continue working on it.

@github-actions github-actions bot closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant