diff --git a/services/web/frontend/js/features/review-panel/context/ranges-context.tsx b/services/web/frontend/js/features/review-panel/context/ranges-context.tsx index 968ed0263e..0ef1ccda50 100644 --- a/services/web/frontend/js/features/review-panel/context/ranges-context.tsx +++ b/services/web/frontend/js/features/review-panel/context/ranges-context.tsx @@ -32,6 +32,8 @@ import { TransactionSpec, } from '@codemirror/state' import { buildRangesFromSnapshot } from '@/features/review-panel/utils/snapshot-ranges' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' +import { useReviewPanelViewContext } from './review-panel-view-context' export type Ranges = { docId: string @@ -106,9 +108,11 @@ export const RangesProvider: FC = ({ children }) => { const { projectId } = useIdeReactContext() const { currentDocument } = useEditorOpenDocContext() const { socket } = useConnectionContext() + const { sendEvent } = useEditorAnalytics() const [ranges, setRanges] = useState(() => buildRanges(currentDocument) ) + const reviewPanelView = useReviewPanelViewContext() // rebuild the ranges when the current doc changes useEffect(() => { @@ -247,6 +251,10 @@ export const RangesProvider: FC = ({ children }) => { } shareDoc.submitOp([op]) + sendEvent('rp-changes-accepted', { + count: changes.length, + view: reviewPanelView, + }) // dispatch an effect as the editor's doc doesn't change when tracked changes are accepted view.dispatch({ @@ -317,6 +325,10 @@ export const RangesProvider: FC = ({ children }) => { } shareDoc.submitOp([op]) + sendEvent('rp-changes-rejected', { + count: changes.length, + view: reviewPanelView, + }) // in case the doc didn't change view.dispatch(...specs, { @@ -333,6 +345,10 @@ export const RangesProvider: FC = ({ children }) => { await postJSON(url, { body: { change_ids: ids } }) currentDocument.ranges.removeChangeIds(ids) setRanges(buildRanges(currentDocument)) + sendEvent('rp-changes-accepted', { + count: ids.length, + view: reviewPanelView, + }) } }, async rejectChanges(...changes) { @@ -341,11 +357,15 @@ export const RangesProvider: FC = ({ children }) => { view.dispatch( rejectChanges(view.state, currentDocument.ranges, ids) ) + sendEvent('rp-changes-rejected', { + count: ids.length, + view: reviewPanelView, + }) } }, } satisfies RangesActions } - }, [currentDocument, projectId, view]) + }, [currentDocument, projectId, view, sendEvent, reviewPanelView]) if (!actions) { return null diff --git a/services/web/frontend/js/features/review-panel/context/review-panel-view-context.tsx b/services/web/frontend/js/features/review-panel/context/review-panel-view-context.tsx index 03d274a8af..f1fa46b489 100644 --- a/services/web/frontend/js/features/review-panel/context/review-panel-view-context.tsx +++ b/services/web/frontend/js/features/review-panel/context/review-panel-view-context.tsx @@ -1,8 +1,8 @@ +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { createContext, - Dispatch, FC, - SetStateAction, + useCallback, useContext, useMemo, useState, @@ -13,7 +13,7 @@ export type View = 'cur_file' | 'overview' export const ReviewPanelViewContext = createContext('cur_file') type ViewActions = { - setView: Dispatch> + setView: (newView: View) => void } const ReviewPanelViewActionsContext = createContext( @@ -24,12 +24,21 @@ export const ReviewPanelViewProvider: FC = ({ children, }) => { const [view, setView] = useState('cur_file') + const { sendEvent } = useEditorAnalytics() + + const handleSetView = useCallback( + (newView: View) => { + sendEvent('rp-subview-change', { subView: newView }) + setView(newView) + }, + [sendEvent] + ) const actions = useMemo( () => ({ - setView, + setView: handleSetView, }), - [setView] + [handleSetView] ) return ( diff --git a/services/web/frontend/js/features/review-panel/context/threads-context.tsx b/services/web/frontend/js/features/review-panel/context/threads-context.tsx index 12f157b0dc..b66d334c44 100644 --- a/services/web/frontend/js/features/review-panel/context/threads-context.tsx +++ b/services/web/frontend/js/features/review-panel/context/threads-context.tsx @@ -33,6 +33,8 @@ import Range from 'overleaf-editor-core/lib/range' import { trackedDeletesFromState } from '@/features/source-editor/utils/tracked-deletes' import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context' import { rangesUpdatedEffect } from '@/features/source-editor/extensions/history-ot' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' +import { useReviewPanelViewContext } from './review-panel-view-context' export type Threads = Record @@ -62,6 +64,8 @@ export const ThreadsProvider: FC = ({ children }) => { const { currentDocument } = useEditorOpenDocContext() const { isRestrictedTokenMember } = useEditorContext() const view = useCodeMirrorViewContext() + const { sendEvent } = useEditorAnalytics() + const reviewPanelView = useReviewPanelViewContext() // const [error, setError] = useState() const [data, setData] = useState() @@ -275,6 +279,8 @@ export const ThreadsProvider: FC = ({ children }) => { body: { content }, }) + sendEvent('rp-new-comment', { size: content.length }) + const op: CommentOperation = { c: text, p: pos, @@ -287,22 +293,31 @@ export const ThreadsProvider: FC = ({ children }) => { await postJSON( `/project/${projectId}/doc/${currentDocument.doc_id}/thread/${threadId}/resolve` ) + sendEvent('rp-comment-resolve', { view: reviewPanelView }) }, async reopenThread(threadId: string) { await postJSON( `/project/${projectId}/doc/${currentDocument.doc_id}/thread/${threadId}/reopen` ) + sendEvent('rp-comment-reopen') }, async deleteThread(threadId: string) { await deleteJSON( `/project/${projectId}/doc/${currentDocument.doc_id}/thread/${threadId}` ) currentDocument.ranges?.removeCommentId(threadId) + sendEvent('rp-comment-delete') }, async addMessage(threadId: ThreadId, content: string) { await postJSON(`/project/${projectId}/thread/${threadId}/messages`, { body: { content }, }) + + sendEvent('rp-comment-reply', { + view, + size: content.length, + thread: threadId, + }) }, async editMessage( threadId: ThreadId, @@ -336,6 +351,8 @@ export const ThreadsProvider: FC = ({ children }) => { body: { content }, }) + sendEvent('rp-new-comment', { size: content.length }) + const trackedDeletes = trackedDeletesFromState(view.state) pos = trackedDeletes.toSnapshot(pos) const ranges = [new Range(pos, text.length)] @@ -348,6 +365,7 @@ export const ThreadsProvider: FC = ({ children }) => { async resolveThread(threadId: string) { const op = new SetCommentStateOperation(threadId, true) currentDocument.historyOTShareDoc.submitOp([op]) + sendEvent('rp-comment-resolve', { view: reviewPanelView }) view.dispatch({ effects: rangesUpdatedEffect.of(null), }) @@ -355,6 +373,7 @@ export const ThreadsProvider: FC = ({ children }) => { async reopenThread(threadId: string) { const op = new SetCommentStateOperation(threadId, false) currentDocument.historyOTShareDoc.submitOp([op]) + sendEvent('rp-comment-reopen') view.dispatch({ effects: rangesUpdatedEffect.of(null), }) @@ -362,6 +381,7 @@ export const ThreadsProvider: FC = ({ children }) => { async deleteThread(threadId: string) { const op = new DeleteCommentOperation(threadId) currentDocument.historyOTShareDoc.submitOp([op]) + sendEvent('rp-comment-delete') view.dispatch({ effects: rangesUpdatedEffect.of(null), }) @@ -370,7 +390,14 @@ export const ThreadsProvider: FC = ({ children }) => { } return actions - }, [view, currentDocument, projectId, isHistoryOT]) + }, [ + view, + reviewPanelView, + currentDocument, + projectId, + isHistoryOT, + sendEvent, + ]) if (!actions) { return null