diff --git a/services/web/frontend/js/features/pdf-preview/util/output-files.js b/services/web/frontend/js/features/pdf-preview/util/output-files.js index 0eca1e6aa0..e7178982c1 100644 --- a/services/web/frontend/js/features/pdf-preview/util/output-files.js +++ b/services/web/frontend/js/features/pdf-preview/util/output-files.js @@ -188,6 +188,26 @@ export const buildRuleCounts = (entries = []) => { return counts } +export const buildRuleDeltas = (ruleCounts, previousRuleCounts) => { + const counts = {} + + // keys that are defined in the current log entries + for (const [key, value] of Object.entries(ruleCounts)) { + const previousValue = previousRuleCounts[key] ?? 0 + counts[`delta_${key}`] = value - previousValue + } + + // keys that are no longer defined in the current log entries + for (const [key, value] of Object.entries(previousRuleCounts)) { + if (!(key in counts)) { + counts[key] = 0 + counts[`delta_${key}`] = -value + } + } + + return counts +} + function buildURL(file, pdfDownloadDomain) { if (file.build && pdfDownloadDomain) { // Downloads from the compiles domain must include a build id. diff --git a/services/web/frontend/js/shared/context/detach-compile-context.tsx b/services/web/frontend/js/shared/context/detach-compile-context.tsx index 9bc4c58280..56f5130955 100644 --- a/services/web/frontend/js/shared/context/detach-compile-context.tsx +++ b/services/web/frontend/js/shared/context/detach-compile-context.tsx @@ -68,6 +68,7 @@ export const DetachCompileProvider: FC = ({ children }) => { setChangedAt: _setChangedAt, clearCache: _clearCache, syncToEntry: _syncToEntry, + recordAction: _recordAction, } = localCompileContext const [animateCompileDropdownArrow] = useDetachStateWatcher( @@ -363,6 +364,13 @@ export const DetachCompileProvider: FC = ({ children }) => { 'detacher' ) + const recordAction = useDetachAction( + 'record-action', + _recordAction, + 'detached', + 'detacher' + ) + useCompileTriggers(startCompile, setChangedAt) useLogEvents(setShowLogs) @@ -420,6 +428,7 @@ export const DetachCompileProvider: FC = ({ children }) => { setChangedAt, cleanupCompileResult, syncToEntry, + recordAction, }), [ animateCompileDropdownArrow, @@ -472,6 +481,7 @@ export const DetachCompileProvider: FC = ({ children }) => { setChangedAt, cleanupCompileResult, syncToEntry, + recordAction, ] ) diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index 38a65659d4..77dd2dc6a7 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -22,6 +22,7 @@ import { import { buildLogEntryAnnotations, buildRuleCounts, + buildRuleDeltas, handleLogFiles, handleOutputFiles, } from '../../features/pdf-preview/util/output-files' @@ -97,6 +98,7 @@ export type CompileContext = { setChangedAt: (value: any) => void clearCache: () => void syncToEntry: (value: any, keepCurrentView?: boolean) => void + recordAction: (action: string) => void } export const LocalCompileContext = createContext( @@ -113,7 +115,7 @@ export const LocalCompileProvider: FC = ({ children }) => { const { pdfPreviewOpen } = useLayoutContext() - const { features, alphaProgram } = useUserContext() + const { features, alphaProgram, labsProgram } = useUserContext() const { fileTreeData } = useFileTreeData() const { findEntityByPath } = useFileTreePathContext() @@ -351,12 +353,23 @@ export const LocalCompileProvider: FC = ({ children }) => { const hasCompileLogsEvents = useFeatureFlag('compile-log-events') + // compare log entry counts with the previous compile, and record actions between compiles + // these are refs rather than state so they don't trigger the effect to run + const previousRuleCountsRef = useRef | null>(null) + const recordedActionsRef = useRef>({}) + const recordAction = useCallback((action: string) => { + recordedActionsRef.current[action] = true + }, []) + // handle the data returned from a compile request // note: this should _only_ run when `data` changes, // the other dependencies must all be static useEffect(() => { const abortController = new AbortController() + const recordedActions = recordedActionsRef.current + recordedActionsRef.current = {} + if (data) { if (data.clsiServerId) { setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController @@ -413,13 +426,26 @@ export const LocalCompileProvider: FC = ({ children }) => { ) } - if (hasCompileLogsEvents) { + if (hasCompileLogsEvents || labsProgram) { + const ruleCounts = buildRuleCounts( + result.logEntries.all + ) as Record + + const previousRuleCounts = previousRuleCountsRef.current + previousRuleCountsRef.current = ruleCounts + + const ruleDeltas = previousRuleCounts + ? buildRuleDeltas(ruleCounts, previousRuleCounts) + : {} + sendMB('compile-log-entries', { status: data.status, stopOnFirstError: data.options.stopOnFirstError, isAutoCompileOnLoad: !!data.options.isAutoCompileOnLoad, isAutoCompileOnChange: !!data.options.isAutoCompileOnChange, - ...buildRuleCounts(result.logEntries.all), + ...recordedActions, + ...ruleCounts, + ...ruleDeltas, }) } } @@ -492,6 +518,8 @@ export const LocalCompileProvider: FC = ({ children }) => { data, ide, alphaProgram, + labsProgram, + features, hasCompileLogsEvents, hasPremiumCompile, isProjectOwner, @@ -664,6 +692,7 @@ export const LocalCompileProvider: FC = ({ children }) => { setChangedAt, cleanupCompileResult, syncToEntry, + recordAction, }), [ animateCompileDropdownArrow, @@ -715,6 +744,7 @@ export const LocalCompileProvider: FC = ({ children }) => { setShowLogs, toggleLogs, syncToEntry, + recordAction, ] )