Record each completion selection for analytics (#13665)

GitOrigin-RevId: bc8e92ceca51f6365c4311204a35fc85914969b0
This commit is contained in:
Alf Eaton 2023-07-11 14:31:36 +01:00 committed by Copybot
parent 5a786029a3
commit 48e758a5fe
9 changed files with 57 additions and 15 deletions

View file

@ -4,7 +4,7 @@ import Icon from '../../../../shared/components/icon'
import useDropdown from '../../../../shared/hooks/use-dropdown'
import Tooltip from '../../../../shared/components/tooltip'
import { EditorView } from '@codemirror/view'
import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import { useCodeMirrorViewContext } from '../codemirror-editor'
import MaterialIcon from '../../../../shared/components/material-icon'
@ -38,7 +38,7 @@ export const ToolbarButtonMenu: FC<{
}}
onClick={event => {
if (event.altKey && altCommand && open === false) {
emitCommandEvent(view, id)
emitToolbarEvent(view, id)
event.preventDefault()
altCommand(view)
view.focus()

View file

@ -4,7 +4,7 @@ import Icon from '../../../../shared/components/icon'
import { useCallback } from 'react'
import { FigureModalSource } from '../figure-modal/figure-modal-context'
import { useTranslation } from 'react-i18next'
import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import { useCodeMirrorViewContext } from '../codemirror-editor'
import { insertFigure } from '../../extensions/toolbar/commands'
@ -13,7 +13,7 @@ export const InsertFigureDropdown = () => {
const view = useCodeMirrorViewContext()
const openFigureModal = useCallback(
(source: FigureModalSource, sourceName: string) => {
emitCommandEvent(view, `toolbar-figure-modal-${sourceName}`)
emitToolbarEvent(view, `toolbar-figure-modal-${sourceName}`)
window.dispatchEvent(
new CustomEvent('figure-modal:open', {
detail: source,

View file

@ -1,6 +1,6 @@
import { ListGroupItem } from 'react-bootstrap'
import { ToolbarButtonMenu } from './button-menu'
import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import MaterialIcon from '../../../../shared/components/material-icon'
import { useTranslation } from 'react-i18next'
import { useCodeMirrorViewContext } from '../codemirror-editor'
@ -23,7 +23,7 @@ export function MathDropdown() {
<ListGroupItem
aria-label={t('toolbar_insert_inline_math')}
onClick={event => {
emitCommandEvent(view, 'toolbar-inline-math')
emitToolbarEvent(view, 'toolbar-inline-math')
event.preventDefault()
wrapInInlineMath(view)
view.focus()
@ -35,7 +35,7 @@ export function MathDropdown() {
<ListGroupItem
aria-label={t('toolbar_insert_display_math')}
onClick={event => {
emitCommandEvent(view, 'toolbar-display-math')
emitToolbarEvent(view, 'toolbar-display-math')
event.preventDefault()
wrapInDisplayMath(view)
view.focus()

View file

@ -11,7 +11,7 @@ import { useCallback, useRef } from 'react'
import { Overlay, Popover } from 'react-bootstrap'
import useEventListener from '../../../../shared/hooks/use-event-listener'
import useDropdown from '../../../../shared/hooks/use-dropdown'
import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import Icon from '../../../../shared/components/icon'
import { useTranslation } from 'react-i18next'
@ -90,7 +90,7 @@ export const SectionHeadingDropdown = () => {
role="menuitem"
key={level}
onClick={() => {
emitCommandEvent(view, 'section-level-change')
emitToolbarEvent(view, 'section-level-change')
setSectionHeadingLevel(view, level)
view.focus()
setOverflowOpen(false)

View file

@ -4,7 +4,7 @@ import { useCodeMirrorViewContext } from '../codemirror-editor'
import { Button } from 'react-bootstrap'
import classnames from 'classnames'
import Tooltip from '../../../../shared/components/tooltip'
import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import Icon from '../../../../shared/components/icon'
export const ToolbarButton = memo<{
@ -38,7 +38,7 @@ export const ToolbarButton = memo<{
const handleClick = useCallback(
event => {
emitCommandEvent(view, id)
emitToolbarEvent(view, id)
if (command) {
event.preventDefault()
command(view)

View file

@ -0,0 +1,28 @@
import { ViewPlugin } from '@codemirror/view'
import { pickedCompletion } from '@codemirror/autocomplete'
import { emitCompletionEvent } from './toolbar/utils/analytics'
/**
* A custom view plugin that watches for transactions with the `pickedCompletion` annotation.
* If the completion label starts with a command, log that command for analytics.
*/
export const completionLogger = ViewPlugin.define(view => {
return {
update(update) {
for (const tr of update.transactions) {
const completion = tr.annotation(pickedCompletion)
if (completion) {
const command = completionCommand(completion.label)
if (command) {
emitCompletionEvent(view, command)
}
}
}
},
}
})
const completionCommand = (label: string): string | null => {
const matches = label.match(/^(\\\w+)/)
return matches ? matches[1] : null
}

View file

@ -48,6 +48,7 @@ import { highlightSpecialChars } from './highlight-special-chars'
import { toolbarPanel } from './toolbar/toolbar-panel'
import { geometryChangeEvent } from './geometry-change-event'
import { isSplitTestEnabled } from '../../../utils/splitTestUtils'
import { completionLogger } from './completion-logger'
const moduleExtensions: Array<() => Extension> = importOverleafModules(
'sourceEditorExtensions'
@ -128,6 +129,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
// The built-in extension that highlights the active line in the gutter.
highlightActiveLineGutter(),
inlineBackground(options.visual.visual),
completionLogger,
codemirrorDevTools(),
exceptionLogger(),
// CodeMirror extensions provided by modules

View file

@ -2,7 +2,19 @@ import { EditorView } from '@codemirror/view'
import { sendMB } from '../../../../../infrastructure/event-tracking'
import { isVisual } from '../../visual/visual'
export function emitCommandEvent(view: EditorView, command: string) {
export function emitCommandEvent(
view: EditorView,
key: string,
command: string
) {
const mode = isVisual(view) ? 'visual' : 'source'
sendMB('codemirror-toolbar-event', { command, mode })
sendMB(key, { command, mode })
}
export function emitToolbarEvent(view: EditorView, command: string) {
emitCommandEvent(view, 'codemirror-toolbar-event', command)
}
export function emitCompletionEvent(view: EditorView, command: string) {
emitCommandEvent(view, 'codemirror-completion-event', command)
}

View file

@ -1,7 +1,7 @@
import { EditorView } from '@codemirror/view'
import { GraphicsWidget } from './graphics'
import { editFigureDataEffect } from '../../figure-modal'
import { emitCommandEvent } from '../../toolbar/utils/analytics'
import { emitToolbarEvent } from '../../toolbar/utils/analytics'
export class EditableGraphicsWidget extends GraphicsWidget {
setEditDispatcher(button: HTMLButtonElement, view: EditorView) {
@ -12,7 +12,7 @@ export class EditableGraphicsWidget extends GraphicsWidget {
event.stopImmediatePropagation()
view.dispatch({ effects: editFigureDataEffect.of(this.figureData) })
window.dispatchEvent(new CustomEvent('figure-modal:open-modal'))
emitCommandEvent(view, 'toolbar-figure-modal-edit')
emitToolbarEvent(view, 'toolbar-figure-modal-edit')
return false
}
} else {