Skip to content

feat(ui): add batch apply pattern to Findings filters#10388

Open
Alan-TheGentleman wants to merge 3 commits intomasterfrom
feat/ui-findings-batch-filters
Open

feat(ui): add batch apply pattern to Findings filters#10388
Alan-TheGentleman wants to merge 3 commits intomasterfrom
feat/ui-findings-batch-filters

Conversation

@Alan-TheGentleman
Copy link
Contributor

@Alan-TheGentleman Alan-TheGentleman commented Mar 19, 2026

Context

Screen.Recording.2026-03-19.at.12.21.12.mov

Implements PROWLER-1228 — Findings Filters Batch Apply with Selection Summary.

Currently, every filter selection in the Findings view triggers an immediate API call to the backend. This causes excessive API calls when configuring multiple filters, poor UX as the table reloads on every change, and unnecessary backend load for intermediate filter states.

Description

Introduces a batch apply pattern for the Findings view filters: users select all desired filters freely without triggering API calls, see a summary of pending selections, and explicitly apply them with a single "Apply Filters" button.

New components:

  • useFilterBatch hook — manages two-state model (pending vs applied/URL) with setPending, applyAll, discardAll, clearAll, hasChanges, changeCount, getFilterValue
  • FilterSummaryStrip — horizontal strip below filter bar showing pending selections as removable chips with human-readable labels
  • ApplyFiltersButton — Apply/Discard button pair with change detection

Modified components (backward compatible):

  • DataTableFilterCustom — new mode prop ("instant" | "batch", default "instant") for opt-in batch behavior. All other views (Scans, Providers, Compliance, Users) continue using instant mode unchanged
  • ProviderTypeSelector / AccountsSelector — new optional onBatchChange + selectedValues props for batch participation. Overview page keeps instant-apply
  • ClearFiltersButton — new onClear + pendingCount props for batch-aware clearing
  • CustomDatePicker / CustomCheckboxMutedFindings — batch support via optional props

Removed:

  • Cross-filter dependency logic between provider type and accounts (auto-clearing)
  • Scan option narrowing via useRelatedFilters in Findings view
  • All filters now show their full option set independently

Tests: 47 new unit tests (175 total, all passing)

Steps to review

  1. Navigate to Findings view
  2. Select multiple filters (severity, status, provider, etc.) — verify NO API calls are made
  3. Verify the summary strip shows pending selections with readable labels (e.g., Provider: AWS, Severity: Critical)
  4. Click "Apply Filters" — verify single API call with all filters
  5. Verify Apply button is disabled when no changes are pending
  6. Click "Clear Filters" — verify all pending selections are cleared (including provider/account)
  7. Click Discard (X) — verify pending state resets to last applied
  8. Reload page — verify filters are preserved from URL params
  9. Navigate to other views (Scans, Providers, Compliance) — verify they still use instant-apply

Checklist

Community Checklist
  • This feature/issue is listed in here or roadmap.prowler.com
  • Is it assigned to me, if not, request it via the issue/feature in here or Prowler Community Slack

UI (if applicable)

  • All issue/task requirements work as expected on the UI
  • Screenshots/Video - Mobile (X < 640px)
  • Screenshots/Video - Tablet (640px > X < 1024px)
  • Screenshots/Video - Desktop (X > 1024px)
  • Ensure new entries are added to ui/CHANGELOG.md

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

- Add useFilterBatch hook for two-state filter management (pending vs applied)
- Add FilterSummaryStrip component showing pending selections as removable chips
- Add ApplyFiltersButton with change detection and discard action
- Add batch mode to DataTableFilterCustom with backward-compatible mode prop
- Wire all Findings filters (including provider/account) through batch mode
- Remove cross-filter dependency logic between provider type and accounts
- Remove scan option narrowing from useRelatedFilters in Findings view
- Add clearAll function for complete pending state reset
- Add 47 unit tests covering hook, components, and batch integration
@Alan-TheGentleman Alan-TheGentleman requested a review from a team as a code owner March 19, 2026 11:24
@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

✅ All necessary CHANGELOG.md files have been updated.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Conflict Markers Resolved

All conflict markers have been successfully resolved in this pull request.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

🔒 Container Security Scan

Image: prowler-ui:d0d2218
Last scan: 2026-03-19 11:44:13 UTC

✅ No Vulnerabilities Detected

The container image passed all security checks. No known CVEs were found.

📋 Resources:

- Remove dead props from FindingsFiltersProps (providerIds, providerDetails, completedScans)
- Make muted filter fully participate in batch flow (remove from EXCLUDED_FROM_BATCH)
- Remove eslint-disable and use searchParams as proper useEffect dependency
- Hide ClearFiltersButton when both pending and URL filter counts are zero
- Simplify handleChipRemove by removing redundant key normalization
- Make useFilterBatch reusable with defaultParams option (remove hardcoded FINDINGS_PATH)
- Simplify CustomDatePicker by deriving date from valueProp in batch mode
- Extract STATUS_DISPLAY as top-level const alongside PROVIDER_TYPE_DISPLAY
- Use const object pattern for DataTableFilterMode type (DATA_TABLE_FILTER_MODE)
* @param filterKey - The raw filter key without "filter[]" wrapper, e.g. "provider_id__in"
* @param values - The selected values array
*/
onBatchChange?: (filterKey: string, values: string[]) => void;

Choose a reason for hiding this comment

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

You could improve the API making a discriminated union between the "two modes" of usage (batched or not) to clarify which props are really needed an avoid coginitive overload and DX.

The same could be applied for other functions like this one with batch mode

onDiscard,
className,
}: ApplyFiltersButtonProps) => {
const label = hasChanges

Choose a reason for hiding this comment

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

You can just leave as

 const label = changeCount > 0
      ? `Apply Filters (${changeCount})`
      : "Apply Filters";

parseDate(searchParams.get("filter[inserted_at]")),
);

// In batch mode: derive the displayed date from the controlled prop.

Choose a reason for hiding this comment

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

localDate is derived state — it mirrors searchParams.get("filter[inserted_at]") and
needs a useEffect to stay in sync. This is a classic React anti-pattern ("state that syncs
with props/external source via effect").

Consider deriving the date directly:

const date = valueProp !== undefined
       ? parseDate(valueProp)
       : parseDate(searchParams.get("filter[inserted_at]"));

@@ -1,6 +1,8 @@
export * from "./apply-filters-button";

Choose a reason for hiding this comment

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

FYI: In general i would avoid barrel files. They avoid proper tree shaking and make extra changes needed to work

* Maps raw filter param keys (e.g. "filter[severity__in]") to human-readable labels.
* Used to render chips in the FilterSummaryStrip.
*/
const FILTER_KEY_LABELS: Record<string, string> = {

Choose a reason for hiding this comment

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

You can improve the key type of the object with two approaches depending on how hard you want to restrict the types:

type FilterKey = `filter[${string}]`;
type FilterParam =
    | "provider_type__in"
    | "provider_id__in"
    | "severity__in"
    | "status__in"
    | "delta__in"
    | "region__in"
    | "service__in"
    | "resource_type__in"
    | "category__in"
    | "resource_groups__in"
    | "scan__in"
    | "inserted_at"
    | "muted";

  type FilterKey = `filter[${FilterParam}]`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants