Skip to content

RHINENG-26121: update manager to use account_advisory table#2253

Open
rverdile wants to merge 1 commit into
RedHatInsights:masterfrom
rverdile:update-manager
Open

RHINENG-26121: update manager to use account_advisory table#2253
rverdile wants to merge 1 commit into
RedHatInsights:masterfrom
rverdile:update-manager

Conversation

@rverdile

@rverdile rverdile commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Secure Coding Practices Checklist GitHub Link

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

Switch advisory listing and export to use precomputed per-workspace advisory counts from the account_advisory table when enabled, while preserving legacy behavior as a fallback.

New Features:

  • Add support for filtering advisories by workspace group name and resolving group names to workspace IDs.
  • Introduce a feature flag to enable using the account_advisory table for advisory queries.

Enhancements:

  • Centralize advisory query resolution logic into a shared helper used by both listing and export handlers.
  • Add helper utilities to detect non-group inventory filters and extract group_name filter values.
  • Extend SQL test data and cache refresh to cover the new account_advisory-based advisory counts.

Tests:

  • Add test coverage for group_name-based advisory filtering, tag-based fallback behavior, workspace name resolution, and the new advisory query resolution paths.

@rverdile rverdile requested a review from a team as a code owner June 30, 2026 18:13
@sourcery-ai

sourcery-ai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR introduces account_advisory-backed advisory queries, adds workspace-name filtering support, and centralizes query resolution logic for both list and export endpoints, with accompanying tests and configuration flags.

File-Level Changes

Change Details Files
Refactor advisory query selection to use account_advisory data when enabled and fall back to legacy/system-level queries otherwise.
  • Replace inline conditional logic in advisoriesCommon with a call to resolveAdvisoriesQuery to decide between account_advisory, cached advisory_account_data, and tagged/system-level queries.
  • Update AdvisoriesExportHandler to reuse resolveAdvisoriesQuery and add error handling for database failures.
  • Preserve legacy cached-count path when EnableAccountAdvisory is disabled and no inventory filters or workspace IDs are present, otherwise use tagged queries.
manager/controllers/advisories.go
manager/controllers/advisories_export.go
Add helpers to support workspace name filtering and account_advisory-based aggregation.
  • Introduce hasNonGroupInventoryFilter and getGroupNameFilterValues helpers to control when account_advisory can be used versus system-level queries.
  • Implement resolveWorkspaceNameToIDs to translate workspace (group) names into workspace IDs, constrained by allowed workspaces.
  • Create buildAccountAdvisorySubquery and buildQueryAdvisoriesFromAccountAdvisory to aggregate per-workspace advisory counts and join them with advisory metadata.
manager/controllers/advisories.go
Extend advisory controller tests to cover group_name filters, tags fallback behavior, legacy path toggling, and workspace name resolution.
  • Add tests validating advisory listing and ID listing when filtering by single and multiple group_name values, including non-matching cases.
  • Add tests to ensure tags and certain inventory filters force system-level queries even when account_advisory is enabled.
  • Add tests for resolving workspace names to IDs and for the legacy path when EnableAccountAdvisory is disabled, as well as export behavior with group_name filters.
manager/controllers/advisories_test.go
Wire up account_advisory caching and feature flag configuration.
  • Extend dev/test_data.sql to clear and refresh account_advisory caches via refresh_account_advisory_caches_multi.
  • Add EnableAccountAdvisory config flag, defaulting to true, to control use of account_advisory-backed queries.
dev/test_data.sql
manager/config/config.go

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

  • In resolveAdvisoriesQuery when EnableAccountAdvisory is true and effectiveWorkspaceIDs is empty, you're building the account-advisory query and then immediately adding Where("FALSE"); consider avoiding the extra query construction by early-returning an empty condition instead of building and then discarding the query.
  • resolveWorkspaceNameToIDs currently sets a Select("DISTINCT si.workspace_id") and then calls Pluck("workspace_id", &ids); the explicit Select with DISTINCT is redundant because Pluck already selects the column, so you could move the DISTINCT into the query (e.g. Distinct("workspace_id")) or drop the manual select entirely.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In resolveAdvisoriesQuery when EnableAccountAdvisory is true and effectiveWorkspaceIDs is empty, you're building the account-advisory query and then immediately adding `Where("FALSE")`; consider avoiding the extra query construction by early-returning an empty condition instead of building and then discarding the query.
- resolveWorkspaceNameToIDs currently sets a `Select("DISTINCT si.workspace_id")` and then calls `Pluck("workspace_id", &ids)`; the explicit `Select` with DISTINCT is redundant because `Pluck` already selects the column, so you could move the DISTINCT into the query (e.g. `Distinct("workspace_id")`) or drop the manual select entirely.

## Individual Comments

### Comment 1
<location path="manager/controllers/advisories.go" line_range="217-219" />
<code_context>
 }

+func resolveAdvisoriesQuery(db *gorm.DB, account int, workspaceIDs []string, filters Filters) (*gorm.DB, error) {
+	if config.EnableAccountAdvisory && !hasNonGroupInventoryFilter(filters) {
+		effectiveWorkspaceIDs := workspaceIDs
+		if groupNames := getGroupNameFilterValues(filters); len(groupNames) > 0 {
+			var err error
+			effectiveWorkspaceIDs, err = resolveWorkspaceNameToIDs(db, account, groupNames, workspaceIDs)
</code_context>
<issue_to_address>
**question:** Clarify behavior when both workspaceIDs and group_name filter are provided

This logic makes `effectiveWorkspaceIDs` the intersection of the explicit `workspaceIDs` and those resolved from `group_name`. That’s a specific choice and differs from "group_name overrides workspaces" or "workspaces only" semantics. Please confirm this intersection behavior is what we want when both filters are provided, as this is the point where an incorrect assumption would lead to subtle filtering bugs.
</issue_to_address>

### Comment 2
<location path="manager/controllers/advisories.go" line_range="289-298" />
<code_context>
+	return nil
+}
+
+func resolveWorkspaceNameToIDs(db *gorm.DB,
+	accountID int,
+	names []string,
+	allowedWorkspaceIDs []string) ([]string, error) {
+	var ids []string
+	query := db.Table("system_inventory si").
+		Select("DISTINCT si.workspace_id").
+		Where("si.rh_account_id = ?", accountID).
+		Where("si.workspace_name IN (?)", names)
+	if len(allowedWorkspaceIDs) > 0 {
+		query = query.Where("si.workspace_id IN (?)", allowedWorkspaceIDs)
+	}
+	err := query.Pluck("workspace_id", &ids).Error
+	return ids, err
+}
</code_context>
<issue_to_address>
**nitpick:** Avoid redundant SELECT clause when using Pluck

In `resolveWorkspaceNameToIDs`, the `Select("DISTINCT si.workspace_id")` is redundant because `Pluck("workspace_id", &ids)` issues its own SELECT and ignores the prior one. If you need distinct IDs, use `query.Distinct().Pluck("workspace_id", &ids)` or remove the explicit `Select` to keep the query clear.
</issue_to_address>

### Comment 3
<location path="manager/controllers/advisories.go" line_range="216" />
<code_context>
 	return query
 }

+func resolveAdvisoriesQuery(db *gorm.DB, account int, workspaceIDs []string, filters Filters) (*gorm.DB, error) {
+	if config.EnableAccountAdvisory && !hasNonGroupInventoryFilter(filters) {
+		effectiveWorkspaceIDs := workspaceIDs
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring `resolveAdvisoriesQuery` to separate workspace/filter normalization from query strategy selection and to hide special-case handling inside builders and higher-level filter helpers for clearer control flow.

The added functionality is solid, but `resolveAdvisoriesQuery` now mixes multiple concerns and conditional branches in one place, which makes it harder to reason about. You can reduce complexity without changing behavior by:

1. **Separating filter/workspace normalization from strategy selection**
2. **Moving the `len(effectiveWorkspaceIDs) == 0` special case into the builder**
3. **Encapsulating the filter-specific helpers behind higher-level names**

### 1) Split `resolveAdvisoriesQuery` into normalization + strategy selection

Right now `resolveAdvisoriesQuery` does both:

- Inspect filters (`hasNonGroupInventoryFilter`, `getGroupNameFilterValues`)
- Resolve workspace IDs from group names
- Choose between account advisory / cached counts / tagged query

You can keep behavior but make the decision tree clearer by extracting the workspace resolution into a small helper, and making `resolveAdvisoriesQuery` mostly about strategy selection:

```go
func resolveAdvisoriesQuery(db *gorm.DB, account int, workspaceIDs []string, filters Filters) (*gorm.DB, error) {
    effectiveWorkspaceIDs, err := resolveEffectiveWorkspaceIDs(db, account, workspaceIDs, filters)
    if err != nil {
        return nil, err
    }

    if config.EnableAccountAdvisory && !hasNonGroupInventoryFilter(filters) {
        middlewares.AdvisoryAccountDataCnt.WithLabelValues("hit").Inc()
        return buildQueryAdvisoriesFromAccountAdvisory(db, account, effectiveWorkspaceIDs), nil
    }

    if !config.EnableAccountAdvisory && !config.DisableCachedCounts &&
        !HasInventoryFilter(filters) && len(workspaceIDs) == 0 {
        middlewares.AdvisoryAccountDataCnt.WithLabelValues("hit").Inc()
        return buildQueryAdvisories(db, account), nil
    }

    middlewares.AdvisoryAccountDataCnt.WithLabelValues("miss").Inc()
    return buildQueryAdvisoriesTagged(db, filters, account, workspaceIDs), nil
}

func resolveEffectiveWorkspaceIDs(db *gorm.DB, account int, workspaceIDs []string, filters Filters) ([]string, error) {
    if groupNames := getGroupNameFilterValues(filters); len(groupNames) > 0 {
        return resolveWorkspaceNameToIDs(db, account, groupNames, workspaceIDs)
    }
    return workspaceIDs, nil
}
```

This preserves the feature flags and behavior, but makes the conditional paths easier to follow.

### 2) Move “no workspace” handling into the account advisory builder

The special-case:

```go
if len(effectiveWorkspaceIDs) == 0 {
    query := buildQueryAdvisoriesFromAccountAdvisory(db, account, effectiveWorkspaceIDs)
    return query.Where("FALSE"), nil
}
```

forces callers to know that “no workspace” means “return nothing”. You can push that logic into the builder so callers don’t need to know the `Where("FALSE")` trick:

```go
func buildQueryAdvisoriesFromAccountAdvisory(db *gorm.DB, account int, workspaceIDs []string) *gorm.DB {
    // No effective workspaces => intentionally empty result
    if len(workspaceIDs) == 0 {
        return database.AdvisoryMetadata(db).
            Select(AdvisoriesSelect).
            Where("FALSE")
    }

    subq := buildAccountAdvisorySubquery(db, account, workspaceIDs)
    return database.AdvisoryMetadata(db).
        Select(AdvisoriesSelect).
        Joins("JOIN (?) aad ON am.id = aad.advisory_id", subq).
        Joins("LEFT JOIN advisory_severity sev ON am.severity_id = sev.id")
}
```

Then `resolveAdvisoriesQuery` just calls the builder without extra branching.

### 3) Wrap filter helpers in higher-level semantics

Right now `resolveAdvisoriesQuery` needs to know about `"group_name"` and the concrete `TagFilter` / `InventoryFilter` types via:

```go
func hasNonGroupInventoryFilter(filters Filters) bool { ... }
func getGroupNameFilterValues(filters Filters) []string { ... }
```

You can keep these helpers but rename / group them to better express intent, e.g.:

```go
func filtersHaveNonGroupInventoryConstraints(filters Filters) bool {
    return hasNonGroupInventoryFilter(filters)
}

func filtersGroupNames(filters Filters) []string {
    return getGroupNameFilterValues(filters)
}
```

and use them in `resolveEffectiveWorkspaceIDs` / `resolveAdvisoriesQuery`:

```go
if config.EnableAccountAdvisory && !filtersHaveNonGroupInventoryConstraints(filters) {
    // ...
}

if groupNames := filtersGroupNames(filters); len(groupNames) > 0 {
    // ...
}
```

Alternatively, place them into a small “filter adapter” file so query resolution doesn’t directly deal with magic keys and raw filter internals.

These focused changes keep all existing feature behavior (including metrics and flags) but reduce the branching and responsibility concentration in `resolveAdvisoriesQuery`, making the code easier to follow and 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.

Comment thread manager/controllers/advisories.go Outdated
Comment thread manager/controllers/advisories.go
Comment thread manager/controllers/advisories.go
@codecov-commenter

codecov-commenter commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.37500% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.92%. Comparing base (48834b4) to head (7a2293e).

Files with missing lines Patch % Lines
manager/controllers/advisories.go 88.33% 4 Missing and 3 partials ⚠️
manager/controllers/advisories_export.go 25.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2253      +/-   ##
==========================================
+ Coverage   58.77%   58.92%   +0.14%     
==========================================
  Files         147      147              
  Lines        9235     9288      +53     
==========================================
+ Hits         5428     5473      +45     
- Misses       3235     3241       +6     
- Partials      572      574       +2     
Flag Coverage Δ
unittests 58.92% <84.37%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rverdile rverdile force-pushed the update-manager branch 2 times, most recently from e7e67fa to 71a590c Compare June 30, 2026 20:12
@TenSt TenSt self-assigned this Jul 2, 2026

@TenSt TenSt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! Please address the comments from soucery-ai (resolve them if you don't want to implement them) and I'm ready to approve 👍

@rverdile

rverdile commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

thanks @TenSt! removed the extra select from the query

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.

3 participants