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 { FilterPanel } from "./filterPanel";
import { useFilteredMatches } from "./filterPanel/useFilterMatches"; import { useFilteredMatches } from "./filterPanel/useFilterMatches";
import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel"; import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel";
import useCaptureEvent from "@/hooks/useCaptureEvent";
interface SearchResultsPageProps { interface SearchResultsPageProps {
searchQuery: string; searchQuery: string;
@ -50,6 +51,7 @@ export const SearchResultsPage = ({
const { setSearchHistory } = useSearchHistory(); const { setSearchHistory } = useSearchHistory();
const domain = useDomain(); const domain = useDomain();
const { toast } = useToast(); const { toast } = useToast();
const captureEvent = useCaptureEvent();
// Encodes the number of matches to return in the search response. // Encodes the number of matches to return in the search response.
const _maxMatchCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.matches) ?? `${defaultMaxMatchCount}`); const _maxMatchCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.matches) ?? `${defaultMaxMatchCount}`);
@ -59,7 +61,8 @@ export const SearchResultsPage = ({
error, error,
files, files,
repoInfo, repoInfo,
durationMs, timeToSearchCompletionMs,
timeToFirstSearchResultMs,
isStreaming, isStreaming,
numMatches, numMatches,
isExhaustive, isExhaustive,
@ -98,40 +101,52 @@ export const SearchResultsPage = ({
]) ])
}, [searchQuery, setSearchHistory]); }, [searchQuery, setSearchHistory]);
// @todo: capture search stats on completion. useEffect(() => {
// useEffect(() => { if (isStreaming || !stats) {
// if (!searchResponse) { return;
// return; }
// }
// const fileLanguages = searchResponse.files?.map(file => file.language) || []; const fileLanguages = files.map(file => file.language) || [];
// captureEvent("search_finished", { console.debug('timeToFirstSearchResultMs:', timeToFirstSearchResultMs);
// durationMs: searchResponse.totalClientSearchDurationMs, console.debug('timeToSearchCompletionMs:', timeToSearchCompletionMs);
// 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]);
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 onLoadMoreResults = useCallback(() => {
const url = createPathWithQueryParams(`/${domain}/search`, const url = createPathWithQueryParams(`/${domain}/search`,
@ -170,7 +185,7 @@ export const SearchResultsPage = ({
onLoadMoreResults={onLoadMoreResults} onLoadMoreResults={onLoadMoreResults}
numMatches={numMatches} numMatches={numMatches}
repoInfo={repoInfo} repoInfo={repoInfo}
searchDurationMs={durationMs} searchDurationMs={timeToSearchCompletionMs}
isStreaming={isStreaming} isStreaming={isStreaming}
searchStats={stats} searchStats={stats}
isMoreResultsButtonVisible={!isExhaustive} isMoreResultsButtonVisible={!isExhaustive}

View file

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

View file

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