@@ -21,19 +21,21 @@ import { FilterPanel } from "./components/filterPanel";
2121import { SearchResultsPanel } from "./components/searchResultsPanel" ;
2222import { useDomain } from "@/hooks/useDomain" ;
2323import { useToast } from "@/components/hooks/use-toast" ;
24- import { RepositoryInfo , SearchResultFile } from "@/features/search/types" ;
24+ import { RepositoryInfo , SearchResultFile , SearchStats } from "@/features/search/types" ;
2525import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle" ;
2626import { useFilteredMatches } from "./components/filterPanel/useFilterMatches" ;
2727import { Button } from "@/components/ui/button" ;
2828import { ImperativePanelHandle } from "react-resizable-panels" ;
29- import { FilterIcon } from "lucide-react" ;
29+ import { AlertTriangleIcon , FilterIcon } from "lucide-react" ;
3030import { useHotkeys } from "react-hotkeys-hook" ;
3131import { useLocalStorage } from "@uidotdev/usehooks" ;
3232import { Tooltip , TooltipContent , TooltipTrigger } from "@/components/ui/tooltip" ;
3333import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint" ;
3434import { SearchBar } from "../components/searchBar" ;
35+ import { CodeSnippet } from "@/app/components/codeSnippet" ;
36+ import { CopyIconButton } from "../components/copyIconButton" ;
3537
36- const DEFAULT_MAX_MATCH_COUNT = 10000 ;
38+ const DEFAULT_MAX_MATCH_COUNT = 500 ;
3739
3840export default function SearchPage ( ) {
3941 // We need a suspense boundary here since we are accessing query params
@@ -58,7 +60,12 @@ const SearchPageInternal = () => {
5860 const _maxMatchCount = parseInt ( useNonEmptyQueryParam ( SearchQueryParams . matches ) ?? `${ DEFAULT_MAX_MATCH_COUNT } ` ) ;
5961 const maxMatchCount = isNaN ( _maxMatchCount ) ? DEFAULT_MAX_MATCH_COUNT : _maxMatchCount ;
6062
61- const { data : searchResponse , isLoading : isSearchLoading , error } = useQuery ( {
63+ const {
64+ data : searchResponse ,
65+ isPending : isSearchPending ,
66+ isFetching : isFetching ,
67+ error
68+ } = useQuery ( {
6269 queryKey : [ "search" , searchQuery , maxMatchCount ] ,
6370 queryFn : ( ) => measure ( ( ) => unwrapServiceError ( search ( {
6471 query : searchQuery ,
@@ -68,14 +75,17 @@ const SearchPageInternal = () => {
6875 } , domain ) ) , "client.search" ) ,
6976 select : ( { data, durationMs } ) => ( {
7077 ...data ,
71- durationMs,
78+ totalClientSearchDurationMs : durationMs ,
7279 } ) ,
7380 enabled : searchQuery . length > 0 ,
7481 refetchOnWindowFocus : false ,
7582 retry : false ,
76- staleTime : Infinity ,
83+ staleTime : 0 ,
7784 } ) ;
7885
86+ console . log ( `isSearchPending` , isSearchPending ) ;
87+ console . log ( `isFetching` , isFetching ) ;
88+
7989 useEffect ( ( ) => {
8090 if ( error ) {
8191 toast ( {
@@ -109,58 +119,31 @@ const SearchPageInternal = () => {
109119 const fileLanguages = searchResponse . files ?. map ( file => file . language ) || [ ] ;
110120
111121 captureEvent ( "search_finished" , {
112- durationMs : searchResponse . durationMs ,
113- fileCount : searchResponse . zoektStats . fileCount ,
114- matchCount : searchResponse . zoektStats . matchCount ,
115- filesSkipped : searchResponse . zoektStats . filesSkipped ,
116- contentBytesLoaded : searchResponse . zoektStats . contentBytesLoaded ,
117- indexBytesLoaded : searchResponse . zoektStats . indexBytesLoaded ,
118- crashes : searchResponse . zoektStats . crashes ,
119- shardFilesConsidered : searchResponse . zoektStats . shardFilesConsidered ,
120- filesConsidered : searchResponse . zoektStats . filesConsidered ,
121- filesLoaded : searchResponse . zoektStats . filesLoaded ,
122- shardsScanned : searchResponse . zoektStats . shardsScanned ,
123- shardsSkipped : searchResponse . zoektStats . shardsSkipped ,
124- shardsSkippedFilter : searchResponse . zoektStats . shardsSkippedFilter ,
125- ngramMatches : searchResponse . zoektStats . ngramMatches ,
126- ngramLookups : searchResponse . zoektStats . ngramLookups ,
127- wait : searchResponse . zoektStats . wait ,
128- matchTreeConstruction : searchResponse . zoektStats . matchTreeConstruction ,
129- matchTreeSearch : searchResponse . zoektStats . matchTreeSearch ,
130- regexpsConsidered : searchResponse . zoektStats . regexpsConsidered ,
131- flushReason : searchResponse . zoektStats . flushReason ,
122+ durationMs : searchResponse . totalClientSearchDurationMs ,
123+ fileCount : searchResponse . stats . fileCount ,
124+ matchCount : searchResponse . stats . totalMatchCount ,
125+ actualMatchCount : searchResponse . stats . actualMatchCount ,
126+ filesSkipped : searchResponse . stats . filesSkipped ,
127+ contentBytesLoaded : searchResponse . stats . contentBytesLoaded ,
128+ indexBytesLoaded : searchResponse . stats . indexBytesLoaded ,
129+ crashes : searchResponse . stats . crashes ,
130+ shardFilesConsidered : searchResponse . stats . shardFilesConsidered ,
131+ filesConsidered : searchResponse . stats . filesConsidered ,
132+ filesLoaded : searchResponse . stats . filesLoaded ,
133+ shardsScanned : searchResponse . stats . shardsScanned ,
134+ shardsSkipped : searchResponse . stats . shardsSkipped ,
135+ shardsSkippedFilter : searchResponse . stats . shardsSkippedFilter ,
136+ ngramMatches : searchResponse . stats . ngramMatches ,
137+ ngramLookups : searchResponse . stats . ngramLookups ,
138+ wait : searchResponse . stats . wait ,
139+ matchTreeConstruction : searchResponse . stats . matchTreeConstruction ,
140+ matchTreeSearch : searchResponse . stats . matchTreeSearch ,
141+ regexpsConsidered : searchResponse . stats . regexpsConsidered ,
142+ flushReason : searchResponse . stats . flushReason ,
132143 fileLanguages,
133144 } ) ;
134145 } , [ captureEvent , searchQuery , searchResponse ] ) ;
135146
136- const { fileMatches, searchDurationMs, totalMatchCount, isBranchFilteringEnabled, repositoryInfo, matchCount } = useMemo ( ( ) => {
137- if ( ! searchResponse ) {
138- return {
139- fileMatches : [ ] ,
140- searchDurationMs : 0 ,
141- totalMatchCount : 0 ,
142- isBranchFilteringEnabled : false ,
143- repositoryInfo : { } ,
144- matchCount : 0 ,
145- } ;
146- }
147-
148- return {
149- fileMatches : searchResponse . files ?? [ ] ,
150- searchDurationMs : Math . round ( searchResponse . durationMs ) ,
151- totalMatchCount : searchResponse . zoektStats . matchCount ,
152- isBranchFilteringEnabled : searchResponse . isBranchFilteringEnabled ,
153- repositoryInfo : searchResponse . repositoryInfo . reduce ( ( acc , repo ) => {
154- acc [ repo . id ] = repo ;
155- return acc ;
156- } , { } as Record < number , RepositoryInfo > ) ,
157- matchCount : searchResponse . stats . matchCount ,
158- }
159- } , [ searchResponse ] ) ;
160-
161- const isMoreResultsButtonVisible = useMemo ( ( ) => {
162- return totalMatchCount > maxMatchCount ;
163- } , [ totalMatchCount , maxMatchCount ] ) ;
164147
165148 const onLoadMoreResults = useCallback ( ( ) => {
166149 const url = createPathWithQueryParams ( `/${ domain } /search` ,
@@ -183,20 +166,27 @@ const SearchPageInternal = () => {
183166 />
184167 </ TopBar >
185168
186- { ( isSearchLoading ) ? (
169+ { ( isSearchPending || isFetching ) ? (
187170 < div className = "flex flex-col items-center justify-center h-full gap-2" >
188171 < SymbolIcon className = "h-6 w-6 animate-spin" />
189172 < p className = "font-semibold text-center" > Searching...</ p >
190173 </ div >
174+ ) : error ? (
175+ < div className = "flex flex-col items-center justify-center h-full gap-2" >
176+ < AlertTriangleIcon className = "h-6 w-6" />
177+ < p className = "font-semibold text-center" > Failed to search</ p >
178+ < p className = "text-sm text-center" > { error . message } </ p >
179+ </ div >
191180 ) : (
192181 < PanelGroup
193- fileMatches = { fileMatches }
194- isMoreResultsButtonVisible = { isMoreResultsButtonVisible }
182+ fileMatches = { searchResponse . files }
183+ isMoreResultsButtonVisible = { searchResponse . isSearchExhaustive === false }
195184 onLoadMoreResults = { onLoadMoreResults }
196- isBranchFilteringEnabled = { isBranchFilteringEnabled }
197- repoInfo = { repositoryInfo }
198- searchDurationMs = { searchDurationMs }
199- numMatches = { matchCount }
185+ isBranchFilteringEnabled = { searchResponse . isBranchFilteringEnabled }
186+ repoInfo = { searchResponse . repositoryInfo }
187+ searchDurationMs = { searchResponse . totalClientSearchDurationMs }
188+ numMatches = { searchResponse . stats . actualMatchCount }
189+ searchStats = { searchResponse . stats }
200190 />
201191 ) }
202192 </ div >
@@ -208,19 +198,21 @@ interface PanelGroupProps {
208198 isMoreResultsButtonVisible ?: boolean ;
209199 onLoadMoreResults : ( ) => void ;
210200 isBranchFilteringEnabled : boolean ;
211- repoInfo : Record < number , RepositoryInfo > ;
201+ repoInfo : RepositoryInfo [ ] ;
212202 searchDurationMs : number ;
213203 numMatches : number ;
204+ searchStats ?: SearchStats ;
214205}
215206
216207const PanelGroup = ( {
217208 fileMatches,
218209 isMoreResultsButtonVisible,
219210 onLoadMoreResults,
220211 isBranchFilteringEnabled,
221- repoInfo,
222- searchDurationMs,
212+ repoInfo : _repoInfo ,
213+ searchDurationMs : _searchDurationMs ,
223214 numMatches,
215+ searchStats,
224216} : PanelGroupProps ) => {
225217 const [ previewedFile , setPreviewedFile ] = useState < SearchResultFile | undefined > ( undefined ) ;
226218 const filteredFileMatches = useFilteredMatches ( fileMatches ) ;
@@ -241,6 +233,17 @@ const PanelGroup = ({
241233 description : "Toggle filter panel" ,
242234 } ) ;
243235
236+ const searchDurationMs = useMemo ( ( ) => {
237+ return Math . round ( _searchDurationMs ) ;
238+ } , [ _searchDurationMs ] ) ;
239+
240+ const repoInfo = useMemo ( ( ) => {
241+ return _repoInfo . reduce ( ( acc , repo ) => {
242+ acc [ repo . id ] = repo ;
243+ return acc ;
244+ } , { } as Record < number , RepositoryInfo > ) ;
245+ } , [ _repoInfo ] ) ;
246+
244247 return (
245248 < ResizablePanelGroup
246249 direction = "horizontal"
@@ -297,7 +300,23 @@ const PanelGroup = ({
297300 order = { 2 }
298301 >
299302 < div className = "py-1 px-2 flex flex-row items-center" >
300- < InfoCircledIcon className = "w-4 h-4 mr-2" />
303+ < Tooltip >
304+ < TooltipTrigger asChild >
305+ < InfoCircledIcon className = "w-4 h-4 mr-2" />
306+ </ TooltipTrigger >
307+ < TooltipContent side = "right" className = "flex flex-col items-start gap-2" >
308+ < div className = "flex flex-row justify-between w-full" >
309+ < p className = "text-md font-medium" > Search stats for nerds</ p >
310+ < CopyIconButton onCopy = { ( ) => {
311+ navigator . clipboard . writeText ( JSON . stringify ( searchStats , null , 2 ) ) ;
312+ return true ;
313+ } } />
314+ </ div >
315+ < CodeSnippet renderNewlines >
316+ { JSON . stringify ( searchStats , null , 2 ) }
317+ </ CodeSnippet >
318+ </ TooltipContent >
319+ </ Tooltip >
301320 {
302321 fileMatches . length > 0 ? (
303322 < p className = "text-sm font-medium" > { `[${ searchDurationMs } ms] Found ${ numMatches } matches in ${ fileMatches . length } ${ fileMatches . length > 1 ? 'files' : 'file' } ` } </ p >
0 commit comments