Files
prowler/ui/hooks/use-related-filters.ts
T
Pablo Fernandez Guerra (PFE) 5b9824c379 feat(ui): filter by provider group across main views (#11659)
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:32:00 +02:00

115 lines
3.7 KiB
TypeScript

import { useSearchParams } from "next/navigation";
import { isScanEntity } from "@/lib/helper-filters";
import {
FILTER_FIELD,
FilterEntity,
FilterParam,
ProviderEntity,
ProviderType,
ScanEntity,
} from "@/types";
interface UseRelatedFiltersProps {
providerIds?: string[];
providerUIDs?: string[];
providerDetails: { [key: string]: FilterEntity }[];
completedScanIds?: string[];
scanDetails?: { [key: string]: ScanEntity }[];
enableScanRelation?: boolean;
providerFilterType?:
| typeof FILTER_FIELD.PROVIDER
| typeof FILTER_FIELD.PROVIDER_UID;
}
/**
* Derives available providers and scans based on the current URL filters.
*
* Pure computation — no effects, no state, no navigation. The returned
* lists update automatically when searchParams change because the component
* re-renders with new searchParams from Next.js.
*
* Cascading filter cleanup is handled atomically by composed filter controls
* such as ProviderAccountSelectors. This avoids the production bug where
* router.push() calls inside useEffect would silently abort pending
* navigations.
*/
export const useRelatedFilters = ({
providerIds = [],
providerUIDs = [],
providerDetails,
completedScanIds = [],
scanDetails = [],
enableScanRelation = false,
providerFilterType = FILTER_FIELD.PROVIDER,
}: UseRelatedFiltersProps) => {
const searchParams = useSearchParams();
const providers = providerIds.length > 0 ? providerIds : providerUIDs;
const providerParam = searchParams.get(
`filter[${providerFilterType}]` satisfies FilterParam,
);
const providerTypeParam = searchParams.get(
`filter[${FILTER_FIELD.PROVIDER_TYPE}]` satisfies FilterParam,
);
const currentProviders = providerParam ? providerParam.split(",") : [];
const currentProviderTypes = providerTypeParam
? (providerTypeParam.split(",") as ProviderType[])
: [];
const getProviderType = (providerKey: string): ProviderType | null => {
const providerDetail = providerDetails.find(
(detail) => Object.keys(detail)[0] === providerKey,
);
if (!providerDetail) return null;
const entity = providerDetail[providerKey];
if (!isScanEntity(entity as ScanEntity)) {
return (entity as ProviderEntity).provider;
}
return null;
};
// Derive available providers filtered by selected provider types
const availableProviders =
currentProviderTypes.length > 0
? providers.filter((key) => {
const providerType = getProviderType(key);
return providerType && currentProviderTypes.includes(providerType);
})
: providers;
// Derive available scans filtered by selected providers and provider types
const availableScans = enableScanRelation
? currentProviders.length > 0 || currentProviderTypes.length > 0
? completedScanIds.filter((scanId) => {
const scanDetail = scanDetails.find(
(detail) => Object.keys(detail)[0] === scanId,
);
if (!scanDetail) return false;
const scanProviderId = scanDetail[scanId]?.providerInfo?.uid ?? null;
const scanProviderType =
(scanDetail[scanId]?.providerInfo?.provider as ProviderType) ??
null;
return (
(currentProviders.length === 0 ||
(scanProviderId && currentProviders.includes(scanProviderId))) &&
(currentProviderTypes.length === 0 ||
(scanProviderType &&
currentProviderTypes.includes(scanProviderType)))
);
})
: completedScanIds
: completedScanIds;
return {
availableProviderIds: providerIds.length > 0 ? availableProviders : [],
availableProviderUIDs: providerUIDs.length > 0 ? availableProviders : [],
availableScans,
};
};