Skip to content

feat(app): remember collapsed pinned filters (#3227) - #3605

Merged
kodiakhq[bot] merged 3 commits into
ClickHouse:mainfrom
bferreirq:feat/remember-collapsed-pinned-filters
Nov 10, 2026
Merged

kodiakhq[bot] merged 3 commits into
ClickHouse:mainfrom
bferreirq:feat/remember-collapsed-pinned-filters

Conversation

@bferreirq

Copy link
Copy Markdown
Contributor

Summary

Closes #3227

We pin a few fields on our main logs source, and every time we open search they all come back expanded, so we collapse them again each time. This keeps a pinned field collapsed across reloads.

  • New useCollapsedPinnedFields(sourceId) hook in searchFilters.tsx: per-source list of collapsed pinned fields in localStorage (hdx-collapsed-pinned-fields), personal like the other sidebar expand states (hdx-filters-expanded, hdx-shared-filters-expanded).
  • In the filters sidebar, a pinned field (personal or shared) that the user collapses stays collapsed on the next load. It still opens when it has an active filter, so a selection is never hidden. Expanding it again removes it from the list.
  • Nested map groups (LogAttributes.*, ...) are unchanged: they keep their own expand logic. Happy to follow up if wanted.

Screenshots or video

Pinned SeverityText, collapsed, then page reloaded (yarn app:dev:local, demo ClickHouse, otel.otel_logs).

Before After
before after

How to reproduce

Starting point: yarn app:dev:local connected to the demo server, Logs source on /search.

On main:

  1. In the Filters sidebar, pin the SeverityText field (pin icon on its row).
  2. Collapse SeverityText.
  3. Reload the page: SeverityText is expanded again.

On this branch:

  1. Same steps: after the reload SeverityText stays collapsed.
  2. Select a value in it, reload: it opens (active filter).
  3. Expand it again, reload: it stays expanded.

How to test on Vercel preview

Preview routes: /search

Steps:

  1. Open /search on the Logs source.
  2. In the Filters sidebar, click the pin icon on the SeverityText field.
  3. Click the SeverityText header to collapse it.
  4. Reload the page.
  5. Verify the SeverityText filter group is collapsed.

References

  • Issue: Remember collapsed pinned filters #3227
  • Tests: packages/app/src/__tests__/pinnedFilters.test.ts (state survives a remount, cleared on expand, kept per source, nothing stored without a source)
  • Related PRs:

A pinned field collapsed in the search sidebar now stays collapsed the
next time the source loads, instead of always opening. The state is
personal and per source, in localStorage (hdx-collapsed-pinned-fields).
A pinned field with an active filter still opens.
@bferreirq
bferreirq requested a review from a team as a code owner November 10, 2026 16:36
@changeset-bot

changeset-bot Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8576157

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added external needs-vouch Author needs a maintainer to vouch for them labels Nov 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi @bferreirq, thanks for the pull request!

Before we review code from a first-time contributor, a maintainer vouches for them, and you're not on our list yet. There's nothing else to file and this PR stays open — a maintainer will look at it, usually within a day or two, and vouch for you right here if it's a genuine attempt at the problem.

To make that quick, check the PR description has:

  • The issue it addresses (ideally one labelled Accepting PR) and why the change is needed
  • Steps to reproduce the problem and verify the fix, starting from https://play.hyperdx.io or yarn app:dev:local connected to the demo server
  • Before/after screenshots if anything in the UI changed

More detail in our contributing guide.

hasSelection)
);
})()}
onExpandedChange={expanded => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

onExpandedChange only writes while the field is pinned, and nothing removes the entry when the pin goes away. That includes toggleFieldPin, toggleSharedFieldPin and resetPersonalPins in searchFilters.tsx. So if you pin X, collapse it, unpin it, and later pin X again, it reloads collapsed even though you never collapsed it as a pinned field that time. Old entries also pile up in hdx-collapsed-pinned-fields. Fix: call setPinnedFieldCollapsed(key, false) when a field is unpinned and on reset, or ignore stored entries for fields that are no longer pinned.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

expect(result[sourceB]).toEqual({ level: ['error'] });
});
});

Copy link
Copy Markdown
Contributor

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 the isDefaultExpanded / onExpandedChange changes in DBSearchPageFilters.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 over forceExpanded in 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 renders DBSearchPageFilters with a pinned field, collapses it, remounts, and checks the filter-group-control expanded 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

Copy link
Copy Markdown
Contributor Author

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.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

2 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 2 minor

1 posted as inline comment(s) on the changed lines. 1 listed below.

Findings outside the changed lines

1 minor
  • 🔵 packages/app/src/__tests__/pinnedFilters.test.ts:93 — The cleanup code in the component (unpin, reset, onExpandedChange) has no tests → The new tests cover the hook in isolation and the pure getFilterGroupDefaultExpanded function. The pruning test passes in its own predicate (f => f === 'SeverityText'). Nothing renders DBSearchPageFilters to check the parts the most recent commit added. A regression would still pass if onExpandedChange failed to persist for a pinned field, if the unpin handlers stopped clearing the entry, or if the reset handlers passed the wrong predicate (for example isFieldPinned and isSharedFieldPinned swapped). Fix: add a component test. Pin a field, collapse it, unpin it, pin it again, and assert it is expanded. Also check that resetting personal pins keeps the collapsed entry of a shared pin.

Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found. The core logic was traced end-to-end by the correctness, kieran-typescript, and julik-frontend-races reviewers and holds up: the getFilterGroupDefaultExpanded precedence (persisted collapse beats forceExpanded, stale entries ignored when not pinned), no spurious localStorage write on mount (the isDefaultExpanded effect only force-expands and never calls onExpandedChange), source-scoped functional setCollapsed updates, and correct inverted prune-on-reset predicates (keep shared-pinned on personal reset, keep personal-pinned on shared reset) were all verified against the code. No P1/P2-severity issues survived re-grading.

🔵 P3 nitpicks (3)
  • packages/app/src/components/DBSearchPageFilters.tsx:1000 — The distributions toggle sits in the always-visible group header, so clicking it on a collapsed pinned field runs toggleShowDistributions → setExpanded(true) → onExpandedChange(true) → setPinnedFieldCollapsed(key, false), permanently erasing the persisted collapse; hiding the distribution again (and onDistributionError) never restores it.
    • Fix: Expand for the distribution peek via setUncontrolledExpanded directly without emitting onExpandedChange, or restore the prior collapse state when the distribution is hidden.
    • julik-frontend-races, correctness
  • packages/app/src/components/DBSearchPageFilters.tsx:1630 — The grouped NestedFilterGroup branch computes its default-expanded state with its own inline logic rather than getFilterGroupDefaultExpanded, so nested pinned fields silently ignore persisted collapse and the expand-precedence rules now live in two divergent places.
    • Fix: Route the nested branch through getFilterGroupDefaultExpanded, or add a comment stating nested groups intentionally opt out of collapse persistence so future precedence edits aren't missed.
    • maintainability
  • packages/app/src/searchFilters.tsx:451 — Collapsed-field arrays under hdx-collapsed-pinned-fields are never garbage-collected: expanding a field leaves an empty-array source entry, and fields unpinned outside the pin-toggle/reset paths leave inert-but-unbounded entries that accumulate per source.
    • Fix: Drop empty source arrays and prune entries for no-longer-pinned fields when writing, or document the growth as acceptable cruft.
    • correctness, kieran-typescript

Reviewers (9): correctness, testing, maintainability, project-standards, agent-native, learnings-researcher, kieran-typescript, julik-frontend-races, previous-comments.

Testing gaps:

  • New DBSearchPageFiltersComponent wiring is exercised only indirectly through unit tests of the pure helper and hook; no test renders the component to cover onExpandedChange → setPinnedFieldCollapsed persistence, the forget-collapse-on-full-unpin branches, or the prune-on-reset predicate pairing (swapping the two predicates would drop the wrong entries without failing any current test).
  • No test covers the distributions-toggle interaction with persisted collapse for a pinned field.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

- Unpinning a field (personal or shared, once no pin is left) or resetting
  pins clears its collapsed state, so pinning it again opens it.
- Move the expand-on-load rule to getFilterGroupDefaultExpanded and test it:
  collapsed pinned field stays closed, active filter still opens it, it wins
  over the Shared Filters section's forceExpanded, stale entries are ignored
  once unpinned.
@vercel

vercel Bot commented Nov 10, 2026

Copy link
Copy Markdown

@bferreirq is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.


const makeFieldPins = (key: string): FieldPinHandlers => ({
onFieldPinClick: () => toggleFieldPin(key),
onFieldPinClick: () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 (usePinnedFiltersApi, searchFilters.tsx:503), so the cleanup never runs in these cases: a teammate unpins a shared field, another tab unpins it, or someone unpins through the NestedFilterGroup handlers at lines 1608–1610. The stale entry stays in hdx-collapsed-pinned-fields. When the field is pinned again, getFilterGroupDefaultExpanded sees isPinned && isCollapsed and opens it collapsed, which goes against the comment's promise that re-pinning "opens it like any newly pinned field". Fix: put the rule in one place instead of in the callers. Either clear the entry when a field becomes pinned, or have useCollapsedPinnedFields drop entries that are not in the merged pinned set (getPinnedFields). Then remove the four call-site checks.

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

@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Nov 10, 2026 4:58pm UTC

Request Review

@brandon-pereira brandon-pereira left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM - thanks for the contribution!

@brandon-pereira

Copy link
Copy Markdown
Member

/vouch @bferreirq

@brandon-pereira brandon-pereira added automerge and removed needs-vouch Author needs a maintainer to vouch for them labels Nov 10, 2026
@github-actions github-actions Bot mentioned this pull request Nov 10, 2026
@kodiakhq
kodiakhq Bot merged commit 467c167 into ClickHouse:main Nov 10, 2026
25 of 27 checks passed
kodiakhq Bot pushed a commit that referenced this pull request Nov 10, 2026
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.

Remember collapsed pinned filters

2 participants