feat: Show pinned filter values while filters are loading (#1308)

Closes HDX-2641

# Summary

With this change, HyperDX will now display pinned filter values as soon as the search page loads, without waiting for the filter values to be queried from ClickHouse. This enables users to quickly apply relevant filters, before the (sometimes very slow) filter values query completes.

## Demo

For this demo, I added an artificial delay to the filter query to simulate an environment where filter queries are slow

https://github.com/user-attachments/assets/6345cb91-7aba-4acc-a832-05efb3bf17d0
This commit is contained in:
Drew Davis 2025-10-28 12:47:27 -04:00 committed by GitHub
parent d5a38c3e05
commit 3ee93ae918
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 5 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
feat: Show pinned filter values while filters are loading

View file

@ -677,8 +677,13 @@ const DBSearchPageFiltersComponent = ({
) => {
return _setFilterValue(property, value, action);
};
const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
usePinnedFilters(sourceId ?? null);
const {
toggleFilterPin,
toggleFieldPin,
isFilterPinned,
isFieldPinned,
pinnedFilters,
} = usePinnedFilters(sourceId ?? null);
const { width, startResize } = useResizable(16, 'left');
const { data: jsonColumns } = useJsonColumns({
@ -740,7 +745,7 @@ const DBSearchPageFiltersComponent = ({
!['body', 'timestamp', '_hdx_body'].includes(path.toLowerCase()),
);
return strings;
}, [data, jsonColumns, filterState, showMoreFields]);
}, [data, jsonColumns, filterState, showMoreFields, isFieldPinned]);
// Special case for live tail
const [dateRange, setDateRange] = useState<[Date, Date]>(
@ -767,6 +772,26 @@ const DBSearchPageFiltersComponent = ({
keys: keysToFetch,
});
// Merge pinned filter values into the queried facets, so that pinned values are always available
const facetsWithPinnedValues = useMemo(() => {
const facetsMap = new Map((facets ?? []).map(f => [f.key, f.value]));
const mergedKeys = new Set<string>([
...facetsMap.keys(),
...Object.keys(pinnedFilters),
]);
return Array.from(mergedKeys).map(key => {
const queriedValues = facetsMap.get(key);
const pinnedValues = pinnedFilters[key];
const mergedValues = new Set<string>([
...(queriedValues ?? []),
...(pinnedValues ?? []),
]);
return { key, value: Array.from(mergedValues) };
});
}, [facets, pinnedFilters]);
const metadata = useMetadataWithSettings();
const [extraFacets, setExtraFacets] = useState<Record<string, string[]>>({});
const [loadMoreLoadingKeys, setLoadMoreLoadingKeys] = useState<Set<string>>(
@ -807,7 +832,7 @@ const DBSearchPageFiltersComponent = ({
const shownFacets = useMemo(() => {
const _facets: { key: string; value: string[] }[] = [];
for (const _facet of facets ?? []) {
for (const _facet of facetsWithPinnedValues ?? []) {
const facet = structuredClone(_facet);
if (jsonColumns?.some(col => facet.key.startsWith(col))) {
facet.key = `toString(${facet.key})`;
@ -866,7 +891,7 @@ const DBSearchPageFiltersComponent = ({
return _facets;
}, [
facets,
facetsWithPinnedValues,
filterState,
tableMetadata,
extraFacets,
@ -982,6 +1007,7 @@ const DBSearchPageFiltersComponent = ({
</Text>
)
)}
{/* Show facets even when loading to ensure pinned filters are visible while loading */}
{shownFacets.map(facet => (
<FilterGroup
key={facet.key}

View file

@ -353,5 +353,6 @@ export function usePinnedFilters(sourceId: string | null) {
isFilterPinned,
isFieldPinned,
getPinnedFields,
pinnedFilters,
};
}