feat(client): analytics search has scope chips and shared highlight - #506
Open
jasnoorgill wants to merge 2 commits into
Open
feat(client): analytics search has scope chips and shared highlight#506jasnoorgill wants to merge 2 commits into
jasnoorgill wants to merge 2 commits into
Conversation
The analytics-page search box used to filter every list-driven table at once. That made the page land in a half-filtered state from a deep link, and stacked empty states across four tables when the query had zero matches. Three changes: 1. Search input + scope chip strip share one rounded container. Five chips (All / Models / Calls / Errors / Keys, default All) drive which tables the active query applies to. Tables outside the active scope render unfiltered; the user is never surprised by a panel shrinking when they typed something. 2. The container and the four list panels share the same highlight ring + shadow + 200ms transition, so the input area and the filtered table read as one visual unit. A panel highlights only when its scope is active AND the filter produced ≥1 match; the highlight drives both the ring around the card and a small "<shown> of <total>" count in the panel header. 3. The Panel wrapper grows an optional highlighted + countLabel prop, so charts and other non-list surfaces keep their old shape unchanged. The four list panels (per-model, recent-calls, recent- errors, usage-by-key) wire the new props + filtered array; the other eight panels ignore them. Filter applies client-side over already-fetched rows so keystrokes don't re-hit the API. Search is not persisted (resets to empty on mount) — same shape as FallbackPage and ProviderList, and avoids landing in a half-filtered state from a deep link. Charts, summary stat cards, and time-series are deliberately NOT filtered: they aggregate over the selected window and filtering them would misrepresent totals. The user's search is for inspecting specific rows, not the aggregate. i18n: nine new keys (searchAriaLabel, searchScopeAriaLabel, searchScopeAll/Models/Calls/Errors/Keys, matchedCount, noMatches) across all six locales (en, es, fr, it, pt-BR, zh-CN). This is the slim scope from the integration branch: 7 files, +353/-56. The per-model/byKey/recent-calls sortable column headers stay on the integration branch for a separate PR if desired.
Bump shared highlight on the search input and the four table panels from ring-2 ring-primary/50 + shadow-lg/10 to ring-4 ring-primary + shadow-xl/30. Both pulse in sync on the same 200ms transition so the input-area + matched panels read as a single grouping. Applied identically to the input row and the Panel wrapper so they stay visually matched; the only difference is the wrapper's rounded-3xl vs the input's rounded-xl which is intentional (the panels are larger cards).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(client): analytics search has scope chips and shared highlight
Summary
The analytics-page search box now filters the four list tables with a scope chip selector (All / Models / Calls / Errors / Keys) and a shared highlight ring on the input row + matched panel. The search is not persisted, charts and summary cards stay unfiltered.
This bundles two commits:
468f033— feat(client): analytics search has scope chips and shared highlight (the feature)3039d4c— fix(client): bolder analytics search highlight (a 2-line visual tweak on top)Motivation
The previous state had the search box filtering every list table at once, with no way to narrow scope. A user typing an IP got four partial results lists, half of which were irrelevant for what they were inspecting. There was also no visual feedback for "which table is this filter actually affecting?" — the user had to read every panel header to know.
Two specific user pain points this fixes:
Deep-link / reload surprise. Search persisted (or didn't, depending on the table), so a deep link to
/analytics?q=gpt-4would land in a half-filtered state with no context. Now the search resets to empty on every page mount, and the chips give an explicit "scope" for the current query.No visual grouping of "active filter". When the search matched rows in, say, the recent-calls table only, there was no visual cue tying the input box to that table. Now the input row and the matched panel share the same primary-tinted ring + shadow + 200ms transition, so they pulse in sync and read as one visual unit.
Changes
client/src/pages/AnalyticsPage.tsx(+339/-56, biggest file in the diff):useMemofromreact,Search+Xfromlucide-react,cnfrom@/lib/utils.makeMatches<T>(getHaystack)— generic helper that lower-cases a row's haystack and substring-matches the trimmed query. Empty query short-circuits totrueso the source array stays untouched in the common no-filter case.Panelwrapper — gained optionalhighlightedandcountLabelprops. Highlighted adds aring-2 ring-primary/50 shadow-lg shadow-primary/10(later bumped toring-4 ring-primary shadow-xl shadow-primary/30in the follow-up) and a200ms transition-shadow. Eight non-list panels (charts, summary, per-platform breakdowns) keep their old shape unchanged.search: string,scope: 'all' | 'models' | 'calls' | 'errors' | 'keys'(default'all'),trimmedQuery(lowered + trimmed once),hasQuery.matchesByModel,matchesByKey,matchesRecentCall,matchesRecentError(useMemoized for stable identity).visibleByModel,visibleErrors,visibleByKey,visibleRecentCalls(filtered arrays).highlightByModel,highlightErrors,highlightByKey,highlightByCalls(each ishasQuery && inScope(kind) && source.length > 0 && visible.length > 0).anyPanelHighlightedis the OR of the four — drives the input row's ring.<SegmentedControl>(range) is wrapped in a flex row with a search-input + scope-chip strip on the left. The chip strip usesrole="group"+aria-label="Filter scope"for screen readers; each chip is a real<button>witharia-pressed. Selected chip usesbg-primary text-primary-foreground border-primary, mirroring the panel ring's primary tint.recentErrors,recentCalls,perModelBreakdown,usageByKey. Each got:highlighted={highlight*}countLabel={highlight* ? t('analytics.matchedCount', { shown: visible*.length, total: source.length }) : undefined}errors/recentCalls.rows/byModel/byKeyto the matchingvisible*arrayvisible*.length === 0empty-state branch that renderst('analytics.noMatches')(separate from the "no data at all" branch which keeps the old copy).Six i18n locale files (
en,es,fr,it,pt-BR,zh-CN): +9 keys eachsearchAriaLabel(input aria-label)searchScopeAriaLabel(chip strip group label)searchScopeAll/Models/Calls/Errors/Keys(chip labels)matchedCount("{shown} of {total}")noMatches(empty-state copy)Test plan
npm run build— full build succeeds (server + client), bundle hashindex-Bxql93PJ.jsincludes"Filter scope"(×1),searchScope*(×6 keys),matchedCount(×10 = 4 countLabel invocations + i18n key + deps),ring-4 ring-primary(×2 = input + Panel wrapper),ring-2 ring-primary/50(×0 = old style fully replaced).npm test— 94 test files, 926 tests passed.t('analytics.noMatches')(no highlight, no count); other in-scope panels with matches still highlight.Compatibility
/api/analytics/*endpoints and their response shapes are unchanged.lucide-reactandcnfrom@/lib/utilsare already used elsewhere in the client (EmbeddingDetailPage,EmbeddingsPage, etc.).Panelsignature change is backward-compatible:highlightedandcountLabelare optional, default false / undefined, and the eight non-list call sites are unchanged.cherry-pick-pr-scope-discipline): same rounded container, samew-56input width, same scope-on-the-right pattern. Power users get one mental model across the dashboard.Out of scope (deliberate)
The integration branch (
integration/all-my-prs) also carries:These are separate features with their own review shape. Keeping this PR focused on the search/scope-chips/highlight bundle keeps the diff reviewable (7 files, +353/-56) and lets each feature land independently upstream. Happy to ship any of those as follow-up PRs if wanted.
Follow-up commit (3039d4c)
A 2-line visual tweak shipped as a follow-up commit on top:
ring-2 ring-primary/50 shadow-lg shadow-primary/10→ring-4 ring-primary shadow-xl shadow-primary/30This makes the highlight pop more obviously against the dashboard background. Keeping it as a separate commit makes the bump easy to revert if reviewers want a more subtle effect.