diff --git a/services/web/frontend/js/features/source-editor/languages/latex/complete.ts b/services/web/frontend/js/features/source-editor/languages/latex/complete.ts index 6c61d149a8..f302168074 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/complete.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/complete.ts @@ -29,6 +29,7 @@ import { } from './completions/apply' import { snippet } from './completions/data/environments' import { syntaxTree } from '@codemirror/language' +import { sendMBSampled } from '@/infrastructure/event-tracking' function blankCompletions(): Completions { return { @@ -206,6 +207,90 @@ export const bibKeyArgumentCompletionSource: CompletionSource = } ) +const debouncedCounter = ( + debounceTime: number +): { + debounceTime: number + counter: number + increment: () => void + reset: () => void +} => { + let timeoutId = 0 + let _counter = 0 + return { + debounceTime, + get counter() { + return _counter + }, + increment() { + if (timeoutId !== 0) { + clearTimeout(timeoutId) + } + timeoutId = window.setTimeout(() => { + _counter += 1 + timeoutId = 0 + }, debounceTime) + }, + reset() { + clearTimeout(timeoutId) + _counter = 0 + timeoutId = 0 + }, + } +} + +const CITE_ANALYTICS_REPORT_TIMEOUT = 4000 + +const analyticsSourceBuilder = (debounceTimes: number[]) => { + let timeoutId = 0 + const counters = debounceTimes.map(debounceTime => { + if (debounceTime >= CITE_ANALYTICS_REPORT_TIMEOUT) { + throw new Error( + `Debounce time ${debounceTime} is greater than the report timeout ${CITE_ANALYTICS_REPORT_TIMEOUT}` + ) + } + return debouncedCounter(debounceTime) + }) + + const incrementCounters = () => { + counters.forEach(counter => counter.increment()) + } + + const resetCounters = () => { + counters.forEach(counter => counter.reset()) + } + + const delayedReport = () => { + if (timeoutId !== 0) { + clearTimeout(timeoutId) + } + timeoutId = window.setTimeout(() => { + const result: Record = {} + counters.forEach(debouncedCounter => { + result[`${debouncedCounter.debounceTime}ms`] = debouncedCounter.counter + }) + sendMBSampled( + 'cite-key-search', + { searchesForDebouncedTime: result }, + 0.01 + ) + timeoutId = 0 + resetCounters() + }, CITE_ANALYTICS_REPORT_TIMEOUT) + } + + return () => { + incrementCounters() + delayedReport() + return null + } +} + +const citeKeyAnalyticsSource = makeMultipleArgumentCompletionSource( + ['BibKeyArgument'], + analyticsSourceBuilder([0, 100, 250, 500]) +) + export const refArgumentCompletionSource: CompletionSource = makeMultipleArgumentCompletionSource( ['RefArgument'], @@ -359,6 +444,7 @@ export const argumentCompletionSources: CompletionSource[] = [ documentClassArgumentCompletionSource, bibliographyArgumentCompletionSource, bibliographyStyleArgumentCompletionSource, + citeKeyAnalyticsSource, ] const commandCompletionSource = (context: CompletionContext) => {