Add back posthog search_finished event

This commit is contained in:
bkellam 2025-11-19 13:45:47 -08:00
parent 9c9b6b9578
commit d55dd13c1b
3 changed files with 79 additions and 44 deletions

View file

@ -32,6 +32,7 @@ import { CodePreviewPanel } from "./codePreviewPanel";
import { FilterPanel } from "./filterPanel";
import { useFilteredMatches } from "./filterPanel/useFilterMatches";
import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
import useCaptureEvent from "@/hooks/useCaptureEvent";
interface SearchResultsPageProps {
searchQuery: string;
@ -50,6 +51,7 @@ export const SearchResultsPage = ({
const { setSearchHistory } = useSearchHistory();
const domain = useDomain();
const { toast } = useToast();
const captureEvent = useCaptureEvent();
// Encodes the number of matches to return in the search response.
const _maxMatchCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.matches) ?? `${defaultMaxMatchCount}`);
@ -59,7 +61,8 @@ export const SearchResultsPage = ({
error,
files,
repoInfo,
durationMs,
timeToSearchCompletionMs,
timeToFirstSearchResultMs,
isStreaming,
numMatches,
isExhaustive,
@ -98,40 +101,52 @@ export const SearchResultsPage = ({
])
}, [searchQuery, setSearchHistory]);
// @todo: capture search stats on completion.
// useEffect(() => {
// if (!searchResponse) {
// return;
// }
useEffect(() => {
if (isStreaming || !stats) {
return;
}
// const fileLanguages = searchResponse.files?.map(file => file.language) || [];
const fileLanguages = files.map(file => file.language) || [];
// captureEvent("search_finished", {
// durationMs: searchResponse.totalClientSearchDurationMs,
// fileCount: searchResponse.stats.fileCount,
// matchCount: searchResponse.stats.totalMatchCount,
// actualMatchCount: searchResponse.stats.actualMatchCount,
// filesSkipped: searchResponse.stats.filesSkipped,
// contentBytesLoaded: searchResponse.stats.contentBytesLoaded,
// indexBytesLoaded: searchResponse.stats.indexBytesLoaded,
// crashes: searchResponse.stats.crashes,
// shardFilesConsidered: searchResponse.stats.shardFilesConsidered,
// filesConsidered: searchResponse.stats.filesConsidered,
// filesLoaded: searchResponse.stats.filesLoaded,
// shardsScanned: searchResponse.stats.shardsScanned,
// shardsSkipped: searchResponse.stats.shardsSkipped,
// shardsSkippedFilter: searchResponse.stats.shardsSkippedFilter,
// ngramMatches: searchResponse.stats.ngramMatches,
// ngramLookups: searchResponse.stats.ngramLookups,
// wait: searchResponse.stats.wait,
// matchTreeConstruction: searchResponse.stats.matchTreeConstruction,
// matchTreeSearch: searchResponse.stats.matchTreeSearch,
// regexpsConsidered: searchResponse.stats.regexpsConsidered,
// flushReason: searchResponse.stats.flushReason,
// fileLanguages,
// });
// }, [captureEvent, searchQuery, searchResponse]);
console.debug('timeToFirstSearchResultMs:', timeToFirstSearchResultMs);
console.debug('timeToSearchCompletionMs:', timeToSearchCompletionMs);
captureEvent("search_finished", {
durationMs: timeToSearchCompletionMs,
timeToSearchCompletionMs,
timeToFirstSearchResultMs,
fileCount: stats.fileCount,
matchCount: stats.totalMatchCount,
actualMatchCount: stats.actualMatchCount,
filesSkipped: stats.filesSkipped,
contentBytesLoaded: stats.contentBytesLoaded,
indexBytesLoaded: stats.indexBytesLoaded,
crashes: stats.crashes,
shardFilesConsidered: stats.shardFilesConsidered,
filesConsidered: stats.filesConsidered,
filesLoaded: stats.filesLoaded,
shardsScanned: stats.shardsScanned,
shardsSkipped: stats.shardsSkipped,
shardsSkippedFilter: stats.shardsSkippedFilter,
ngramMatches: stats.ngramMatches,
ngramLookups: stats.ngramLookups,
wait: stats.wait,
matchTreeConstruction: stats.matchTreeConstruction,
matchTreeSearch: stats.matchTreeSearch,
regexpsConsidered: stats.regexpsConsidered,
flushReason: stats.flushReason,
fileLanguages,
isSearchExhaustive: isExhaustive,
});
}, [
captureEvent,
files,
isStreaming,
isExhaustive,
stats,
timeToSearchCompletionMs,
timeToFirstSearchResultMs,
]);
const onLoadMoreResults = useCallback(() => {
const url = createPathWithQueryParams(`/${domain}/search`,
@ -170,7 +185,7 @@ export const SearchResultsPage = ({
onLoadMoreResults={onLoadMoreResults}
numMatches={numMatches}
repoInfo={repoInfo}
searchDurationMs={durationMs}
searchDurationMs={timeToSearchCompletionMs}
isStreaming={isStreaming}
searchStats={stats}
isMoreResultsButtonVisible={!isExhaustive}

View file

@ -8,7 +8,8 @@ interface CacheEntry {
files: SearchResultFile[];
repoInfo: Record<number, RepositoryInfo>;
numMatches: number;
durationMs: number;
timeToSearchCompletionMs: number;
timeToFirstSearchResultMs: number;
timestamp: number;
isExhaustive: boolean;
}
@ -39,7 +40,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
error: Error | null,
files: SearchResultFile[],
repoInfo: Record<number, RepositoryInfo>,
durationMs: number,
timeToSearchCompletionMs: number,
timeToFirstSearchResultMs: number,
numMatches: number,
stats?: SearchStats,
}>({
@ -48,7 +50,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
error: null,
files: [],
repoInfo: {},
durationMs: 0,
timeToSearchCompletionMs: 0,
timeToFirstSearchResultMs: 0,
numMatches: 0,
stats: undefined,
});
@ -94,7 +97,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
error: null,
files: cachedEntry.files,
repoInfo: cachedEntry.repoInfo,
durationMs: cachedEntry.durationMs,
timeToSearchCompletionMs: cachedEntry.timeToSearchCompletionMs,
timeToFirstSearchResultMs: cachedEntry.timeToFirstSearchResultMs,
numMatches: cachedEntry.numMatches,
});
return;
@ -106,7 +110,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
error: null,
files: [],
repoInfo: {},
durationMs: 0,
timeToSearchCompletionMs: 0,
timeToFirstSearchResultMs: 0,
numMatches: 0,
});
@ -138,6 +143,7 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let numMessagesProcessed = 0;
while (true as boolean) {
const { done, value } = await reader.read();
@ -175,6 +181,7 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
}
const response: StreamedSearchResponse = JSON.parse(data);
const isFirstMessage = numMessagesProcessed === 0;
switch (response.type) {
case 'chunk':
setState(prev => ({
@ -191,6 +198,9 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
}, {} as Record<number, RepositoryInfo>),
},
numMatches: prev.numMatches + response.stats.actualMatchCount,
...(isFirstMessage ? {
timeToFirstSearchResultMs: performance.now() - startTime,
} : {}),
}));
break;
case 'final':
@ -198,13 +208,18 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
...prev,
isExhaustive: response.isSearchExhaustive,
stats: response.accumulatedStats,
...(isFirstMessage ? {
timeToFirstSearchResultMs: performance.now() - startTime,
} : {}),
}));
break;
}
numMessagesProcessed++;
}
}
const durationMs = performance.now() - startTime;
const timeToSearchCompletionMs = performance.now() - startTime;
setState(prev => {
// Cache the final results after the stream has completed.
searchCache.set(cacheKey, {
@ -212,12 +227,13 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
repoInfo: prev.repoInfo,
isExhaustive: prev.isExhaustive,
numMatches: prev.numMatches,
durationMs,
timeToFirstSearchResultMs: prev.timeToFirstSearchResultMs,
timeToSearchCompletionMs,
timestamp: Date.now(),
});
return {
...prev,
durationMs,
timeToSearchCompletionMs,
isStreaming: false,
}
});
@ -229,11 +245,11 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
console.error(error);
Sentry.captureException(error);
const durationMs = performance.now() - startTime;
const timeToSearchCompletionMs = performance.now() - startTime;
setState(prev => ({
...prev,
isStreaming: false,
durationMs,
timeToSearchCompletionMs,
error: error as Error,
}));
}

View file

@ -5,7 +5,10 @@ export type PosthogEventMap = {
contentBytesLoaded: number,
indexBytesLoaded: number,
crashes: number,
/** @deprecated: use timeToFirstSearchResultMs and timeToSearchCompletionMs instead */
durationMs: number,
timeToFirstSearchResultMs: number,
timeToSearchCompletionMs: number,
fileCount: number,
shardFilesConsidered: number,
filesConsidered: number,
@ -23,7 +26,8 @@ export type PosthogEventMap = {
matchTreeSearch: number,
regexpsConsidered: number,
flushReason: number,
fileLanguages: string[]
fileLanguages: string[],
isSearchExhaustive: boolean
},
share_link_created: {},
////////////////////////////////////////////////////////////////