feat(ui): add batch apply pattern to Findings filters#10388
feat(ui): add batch apply pattern to Findings filters#10388Alan-TheGentleman wants to merge 3 commits intomasterfrom
Conversation
- 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
|
✅ All necessary |
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
🔒 Container Security ScanImage: ✅ No Vulnerabilities DetectedThe 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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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"; | |||
There was a problem hiding this comment.
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> = { |
There was a problem hiding this comment.
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}]`;
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:
useFilterBatchhook — manages two-state model (pending vs applied/URL) withsetPending,applyAll,discardAll,clearAll,hasChanges,changeCount,getFilterValueFilterSummaryStrip— horizontal strip below filter bar showing pending selections as removable chips with human-readable labelsApplyFiltersButton— Apply/Discard button pair with change detectionModified components (backward compatible):
DataTableFilterCustom— newmodeprop ("instant"|"batch", default"instant") for opt-in batch behavior. All other views (Scans, Providers, Compliance, Users) continue using instant mode unchangedProviderTypeSelector/AccountsSelector— new optionalonBatchChange+selectedValuesprops for batch participation. Overview page keeps instant-applyClearFiltersButton— newonClear+pendingCountprops for batch-aware clearingCustomDatePicker/CustomCheckboxMutedFindings— batch support via optional propsRemoved:
useRelatedFiltersin Findings viewTests: 47 new unit tests (175 total, all passing)
Steps to review
Provider: AWS,Severity: Critical)Checklist
Community Checklist
UI (if applicable)
License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.