Repository navigation
feat(app): remember collapsed pinned filters (#3227) #3605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| '@hyperdx/app': patch | ||
| --- | ||
|
|
||
| feat: A pinned filter collapsed in the search sidebar stays collapsed the next | ||
| time the source loads, instead of always opening |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,6 +65,7 @@ import { usePinnedFiltersApi } from '@/pinnedFilters'; | |
| import { | ||
| FilterStateHook, | ||
| IS_ROOT_SPAN_COLUMN_NAME, | ||
| useCollapsedPinnedFields, | ||
| usePinnedFilters, | ||
| } from '@/searchFilters'; | ||
| import { useSource } from '@/source'; | ||
|
|
@@ -918,6 +919,31 @@ function FilterGroupActions({ | |
|
|
||
| const voidFunc = () => {}; | ||
|
|
||
| /** | ||
| * Whether a filter group opens on load. A pinned field the user collapsed | ||
| * stays collapsed unless it has an active filter. That persisted collapse | ||
| * intentionally takes precedence over a section-level `forceExpanded` | ||
| * (the Shared Filters section). | ||
| */ | ||
| export function getFilterGroupDefaultExpanded({ | ||
| hasSelection, | ||
| isPinned, | ||
| isCollapsed, | ||
| isPrimary, | ||
| forceExpanded, | ||
| }: { | ||
| hasSelection: boolean; | ||
| isPinned: boolean; | ||
| isCollapsed: boolean; | ||
| isPrimary: boolean; | ||
| forceExpanded?: boolean; | ||
| }): boolean { | ||
| if (isPinned && isCollapsed && !hasSelection) { | ||
| return false; | ||
| } | ||
| return forceExpanded ?? (isPrimary || isPinned || hasSelection); | ||
| } | ||
|
|
||
| export const FilterGroup = ({ | ||
| name, | ||
| options, | ||
|
|
@@ -1172,6 +1198,11 @@ const DBSearchPageFiltersComponent = ({ | |
| hasPersonalPins, | ||
| hasSharedPins, | ||
| } = usePinnedFilters(sourceId ?? null); | ||
| const { | ||
| isPinnedFieldCollapsed, | ||
| setPinnedFieldCollapsed, | ||
| pruneCollapsedPinnedFields, | ||
| } = useCollapsedPinnedFields(sourceId ?? null); | ||
| const { data: pinnedFiltersApiData } = usePinnedFiltersApi(sourceId ?? null); | ||
| const [isSharedFiltersVisible, setSharedFiltersVisible] = useLocalStorage( | ||
| 'hdx-shared-filters-visible', | ||
|
|
@@ -1504,6 +1535,8 @@ const DBSearchPageFiltersComponent = ({ | |
| const { keyPrefix = '', isDefaultExpanded: forceExpanded } = | ||
| options ?? {}; | ||
| const { grouped, nonGrouped } = groupFacetsByBaseName(facets); | ||
| const isFieldPinnedAny = (key: string) => | ||
| isFieldPinned(key) || isSharedFieldPinned(key); | ||
|
|
||
| const makeValuePins = (key: string): ValuePinHandlers => ({ | ||
| onPinClick: (value: string | boolean) => toggleFilterPin(key, value), | ||
|
|
@@ -1515,9 +1548,21 @@ const DBSearchPageFiltersComponent = ({ | |
| }); | ||
|
|
||
| const makeFieldPins = (key: string): FieldPinHandlers => ({ | ||
| onFieldPinClick: () => toggleFieldPin(key), | ||
| onFieldPinClick: () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Collapsed state is only cleared on unpins made in this tab, so a re-pinned field can come back collapsed The cleanup runs inside four hand-wired handlers: the two unpin handlers here and the two reset handlers at lines 1794–1802. Shared pins are stored on the team ( Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond |
||
| // Unpinned for good: forget its collapsed state, so pinning it again | ||
| // opens it like any newly pinned field. | ||
| if (isFieldPinned(key) && !isSharedFieldPinned(key)) { | ||
| setPinnedFieldCollapsed(key, false); | ||
| } | ||
| toggleFieldPin(key); | ||
| }, | ||
| isFieldPinned: isFieldPinned(key), | ||
| onToggleSharedFieldPin: () => toggleSharedFieldPin(key), | ||
| onToggleSharedFieldPin: () => { | ||
| if (isSharedFieldPinned(key) && !isFieldPinned(key)) { | ||
| setPinnedFieldCollapsed(key, false); | ||
| } | ||
| toggleSharedFieldPin(key); | ||
| }, | ||
| isSharedFieldPinned: isSharedFieldPinned(key), | ||
| }); | ||
|
|
||
|
|
@@ -1641,17 +1686,23 @@ const DBSearchPageFiltersComponent = ({ | |
| hasLoadedMore={extraFacetKeys.has(facet.key)} | ||
| isDefaultExpanded={(() => { | ||
| const entry = getFilterStateEntry(filterState, facet.key); | ||
| return ( | ||
| forceExpanded ?? | ||
| (isFieldPrimary(tableMetadata, facet.key) || | ||
| isFieldPinned(facet.key) || | ||
| isSharedFieldPinned(facet.key) || | ||
| (entry != null && | ||
| (entry.included.size > 0 || | ||
| entry.excluded.size > 0 || | ||
| entry.range != null))) | ||
| ); | ||
| return getFilterGroupDefaultExpanded({ | ||
| hasSelection: | ||
| entry != null && | ||
| (entry.included.size > 0 || | ||
| entry.excluded.size > 0 || | ||
| entry.range != null), | ||
| isPinned: isFieldPinnedAny(facet.key), | ||
| isCollapsed: isPinnedFieldCollapsed(facet.key), | ||
| isPrimary: !!isFieldPrimary(tableMetadata, facet.key), | ||
| forceExpanded, | ||
| }); | ||
| })()} | ||
| onExpandedChange={expanded => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 minor — Collapsed state stays stored after the field is unpinned, so it comes back when the field is pinned again
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in f1a4b17: unpinning a field (once no personal or shared pin is left) or resetting pins now clears its collapsed state, so pinning it again opens it. |
||
| if (isFieldPinnedAny(facet.key)) { | ||
| setPinnedFieldCollapsed(facet.key, !expanded); | ||
| } | ||
| }} | ||
| chartConfig={chartConfig} | ||
| isLive={isLive} | ||
| onRangeChange={range => setFilterRange(facet.key, range)} | ||
|
|
@@ -1673,6 +1724,8 @@ const DBSearchPageFiltersComponent = ({ | |
| isFieldPinned, | ||
| toggleSharedFieldPin, | ||
| isSharedFieldPinned, | ||
| isPinnedFieldCollapsed, | ||
| setPinnedFieldCollapsed, | ||
| onColumnToggle, | ||
| displayedColumns, | ||
| loadMoreFacetsForKey, | ||
|
|
@@ -1738,9 +1791,15 @@ const DBSearchPageFiltersComponent = ({ | |
| showFilterCounts={showFilterCounts} | ||
| onShowFilterCountsChange={setShowFilterCounts} | ||
| hasPersonalPins={hasPersonalPins} | ||
| onResetPersonalPins={resetPersonalPins} | ||
| onResetPersonalPins={() => { | ||
| pruneCollapsedPinnedFields(isSharedFieldPinned); | ||
| resetPersonalPins(); | ||
| }} | ||
| hasSharedPins={hasSharedPins} | ||
| onResetSharedFilters={resetSharedFilters} | ||
| onResetSharedFilters={() => { | ||
| pruneCollapsedPinnedFields(isFieldPinned); | ||
| resetSharedFilters(); | ||
| }} | ||
| showAllValues={showAllValues} | ||
| onShowAllValuesChange={setShowAllValues} | ||
| /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 minor — Tests only cover the localStorage hook, not the new expand rules in the sidebar
The new tests only check
useCollapsedPinnedFields. The real behaviour lives in theisDefaultExpanded/onExpandedChangechanges inDBSearchPageFilters.tsx, and none of it is tested: a collapsed pinned field renders collapsed, it still opens when it has a selection, the collapsed state now takes priority overforceExpandedin the Shared Filters section, and unpinned fields are never stored. Any of these could break and the tests would still pass. Add a component test that rendersDBSearchPageFilterswith a pinned field, collapses it, remounts, and checks thefilter-group-controlexpanded state, both with and without an active filter.Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in f1a4b17: the expand-on-load rule now lives in getFilterGroupDefaultExpanded, with tests for a collapsed pinned field staying closed, an active filter still opening it, precedence over the Shared Filters forceExpanded, and stale entries ignored once unpinned. I didn't add a full DBSearchPageFilters render test since no existing test mounts the whole sidebar (it needs metadata, facets and pinned-filters API mocks); happy to add one if a maintainer wants it.