Merge pull request #8361 from overleaf/em-halt-on-error-events

Analytics events for "Stop on first error"

GitOrigin-RevId: cbdb054cdf031f0364390c36cc1c1865169f2cd4
This commit is contained in:
Eric Mc Sween 2022-06-13 08:08:38 -04:00 committed by Copybot
parent 5231747243
commit 3d0f464637
6 changed files with 159 additions and 99 deletions

View file

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import { memo } from 'react' import { memo } from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
import PdfCompileButtonInner from './pdf-compile-button-inner' import PdfCompileButtonInner from './pdf-compile-button-inner'
import getMeta from '../../../utils/meta' import getMeta from '../../../utils/meta'
@ -16,7 +17,6 @@ function PdfCompileButton() {
hasChanges, hasChanges,
setAutoCompile, setAutoCompile,
setDraft, setDraft,
setStopOnFirstError,
setStopOnValidationError, setStopOnValidationError,
stopOnFirstError, stopOnFirstError,
stopOnValidationError, stopOnValidationError,
@ -24,6 +24,8 @@ function PdfCompileButton() {
stopCompile, stopCompile,
recompileFromScratch, recompileFromScratch,
} = useCompileContext() } = useCompileContext()
const { enableStopOnFirstError, disableStopOnFirstError } =
useStopOnFirstError({ eventSource: 'dropdown' })
const { t } = useTranslation() const { t } = useTranslation()
const showStopOnFirstError = getMeta('ol-showStopOnFirstError') const showStopOnFirstError = getMeta('ol-showStopOnFirstError')
@ -90,14 +92,14 @@ function PdfCompileButton() {
)} )}
{showStopOnFirstError && ( {showStopOnFirstError && (
<MenuItem onSelect={() => setStopOnFirstError(true)}> <MenuItem onSelect={enableStopOnFirstError}>
<Icon type={stopOnFirstError ? 'check' : ''} fw /> <Icon type={stopOnFirstError ? 'check' : ''} fw />
{t('stop_on_first_error')} {t('stop_on_first_error')}
</MenuItem> </MenuItem>
)} )}
{showStopOnFirstError && ( {showStopOnFirstError && (
<MenuItem onSelect={() => setStopOnFirstError(false)}> <MenuItem onSelect={disableStopOnFirstError}>
<Icon type={!stopOnFirstError ? 'check' : ''} fw /> <Icon type={!stopOnFirstError ? 'check' : ''} fw />
{t('try_to_compile_despite_errors')} {t('try_to_compile_despite_errors')}
</MenuItem> </MenuItem>

View file

@ -1,15 +1,14 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { useTranslation, Trans } from 'react-i18next' import { useTranslation, Trans } from 'react-i18next'
import { memo } from 'react' import { memo, useCallback } from 'react'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import PdfLogEntry from './pdf-log-entry' import PdfLogEntry from './pdf-log-entry'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
import getMeta from '../../../utils/meta' import getMeta from '../../../utils/meta'
function PdfPreviewError({ error }) { function PdfPreviewError({ error }) {
const { t } = useTranslation() const { t } = useTranslation()
const { lastCompileOptions, setStopOnFirstError, startCompile } =
useCompileContext()
switch (error) { switch (error) {
case 'rendering-error': case 'rendering-error':
@ -75,82 +74,8 @@ function PdfPreviewError({ error }) {
</ErrorLogEntry> </ErrorLogEntry>
) )
case 'timedout': { case 'timedout':
const showStopOnFirstError = getMeta('ol-showStopOnFirstError') return <TimedOutLogEntry />
if (showStopOnFirstError) {
return (
<ErrorLogEntry title={t('timedout')}>
<p>{t('project_timed_out_intro')}</p>
<ul>
<li>
<Trans
i18nKey="project_timed_out_optimize_images"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Optimising_very_large_image_files" />,
]}
/>
</li>
<li>
<Trans
i18nKey="project_timed_out_fatal_error"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F#Fatal_compile_errors_blocking_the_compilation" />,
]}
/>
{!lastCompileOptions.stopOnFirstError && (
<>
{' '}
<Trans
i18nKey="project_timed_out_enable_stop_on_first_error"
components={[
// eslint-disable-next-line react/jsx-key
<Button
bsSize="xs"
bsStyle="info"
onClick={() => {
startCompile({ stopOnFirstError: true })
setStopOnFirstError(true)
}}
/>,
]}
/>
</>
)}
</li>
</ul>
<p>
<Trans
i18nKey="project_timed_out_learn_more"
components={{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F" />
),
}}
/>
</p>
</ErrorLogEntry>
)
} else {
return (
<ErrorLogEntry title={t('timedout')}>
{t('proj_timed_out_reason')}
<div>
<a
href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F"
target="_blank"
rel="noopener"
>
{t('learn_how_to_make_documents_compile_quickly')}
</a>
</div>
</ErrorLogEntry>
)
}
}
case 'failure': case 'failure':
return ( return (
@ -233,8 +158,93 @@ function ErrorLogEntry({ title, children }) {
/> />
) )
} }
ErrorLogEntry.propTypes = { ErrorLogEntry.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
children: PropTypes.any.isRequired, children: PropTypes.any.isRequired,
} }
function TimedOutLogEntry() {
const { t } = useTranslation()
const { enableStopOnFirstError } = useStopOnFirstError({
eventSource: 'timeout',
})
const { startCompile, lastCompileOptions } = useCompileContext()
const showStopOnFirstError = getMeta('ol-showStopOnFirstError')
const handleEnableStopOnFirstErrorClick = useCallback(() => {
enableStopOnFirstError()
startCompile({ stopOnFirstError: true })
}, [enableStopOnFirstError, startCompile])
if (showStopOnFirstError) {
return (
<ErrorLogEntry title={t('timedout')}>
<p>{t('project_timed_out_intro')}</p>
<ul>
<li>
<Trans
i18nKey="project_timed_out_optimize_images"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Optimising_very_large_image_files" />,
]}
/>
</li>
<li>
<Trans
i18nKey="project_timed_out_fatal_error"
components={[
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F#Fatal_compile_errors_blocking_the_compilation" />,
]}
/>
{!lastCompileOptions.stopOnFirstError && (
<>
{' '}
<Trans
i18nKey="project_timed_out_enable_stop_on_first_error"
components={[
// eslint-disable-next-line react/jsx-key
<Button
bsSize="xs"
bsStyle="info"
onClick={handleEnableStopOnFirstErrorClick}
/>,
]}
/>
</>
)}
</li>
</ul>
<p>
<Trans
i18nKey="project_timed_out_learn_more"
components={{
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F" />
),
}}
/>
</p>
</ErrorLogEntry>
)
} else {
return (
<ErrorLogEntry title={t('timedout')}>
{t('proj_timed_out_reason')}
<div>
<a
href="https://www.overleaf.com/learn/how-to/Why_do_I_keep_getting_the_compile_timeout_error_message%3F"
target="_blank"
rel="noopener"
>
{t('learn_how_to_make_documents_compile_quickly')}
</a>
</div>
</ErrorLogEntry>
)
}
}
TimedOutLogEntry.propTypes = {}

View file

@ -1,11 +1,22 @@
import { useCallback } from 'react'
import { useTranslation, Trans } from 'react-i18next' import { useTranslation, Trans } from 'react-i18next'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import PdfLogEntry from './pdf-log-entry' import PdfLogEntry from './pdf-log-entry'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
export default function StopOnFirstErrorPrompt() { export default function StopOnFirstErrorPrompt() {
const { t } = useTranslation() const { t } = useTranslation()
const { setStopOnFirstError, startCompile } = useCompileContext() const { startCompile } = useCompileContext()
const { disableStopOnFirstError } = useStopOnFirstError({
eventSource: 'logs-pane',
})
const handleDisableButtonClick = useCallback(() => {
disableStopOnFirstError()
startCompile({ stopOnFirstError: false })
}, [disableStopOnFirstError, startCompile])
return ( return (
<PdfLogEntry <PdfLogEntry
headerTitle={t('stop_on_first_error_enabled_title')} headerTitle={t('stop_on_first_error_enabled_title')}
@ -16,14 +27,7 @@ export default function StopOnFirstErrorPrompt() {
// eslint-disable-next-line react/jsx-key // eslint-disable-next-line react/jsx-key
components={[<strong />]} components={[<strong />]}
/>{' '} />{' '}
<Button <Button bsSize="xs" bsStyle="info" onClick={handleDisableButtonClick}>
bsSize="xs"
bsStyle="info"
onClick={() => {
startCompile({ stopOnFirstError: false })
setStopOnFirstError(false)
}}
>
{t('disable_stop_on_first_error')} {t('disable_stop_on_first_error')}
</Button> </Button>
</> </>

View file

@ -1,3 +1,4 @@
import { useCallback } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
@ -5,18 +6,26 @@ import PreviewLogEntryHeader from './preview-log-entry-header'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import getMeta from '../../../utils/meta' import getMeta from '../../../utils/meta'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) { function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
const { t } = useTranslation() const { t } = useTranslation()
const showStopOnFirstError = getMeta('ol-showStopOnFirstError') const showStopOnFirstError = getMeta('ol-showStopOnFirstError')
const { startCompile, setStopOnFirstError, stoppedOnFirstError } = const { startCompile, stoppedOnFirstError } = useCompileContext()
useCompileContext() const { enableStopOnFirstError } = useStopOnFirstError({
eventSource: 'too-many-logs',
})
const title = t('log_entry_maximum_entries_title', { const title = t('log_entry_maximum_entries_title', {
total: totalEntries, total: totalEntries,
displayed: entriesShown, displayed: entriesShown,
}) })
const handleEnableStopOnFirstErrorClick = useCallback(() => {
enableStopOnFirstError()
startCompile({ stopOnFirstError: true })
}, [enableStopOnFirstError, startCompile])
return ( return (
<div className="log-entry" aria-label={t('log_entry_maximum_entries')}> <div className="log-entry" aria-label={t('log_entry_maximum_entries')}>
<PreviewLogEntryHeader level="raw" headerTitle={title} /> <PreviewLogEntryHeader level="raw" headerTitle={title} />
@ -35,10 +44,7 @@ function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
<Button <Button
bsSize="xs" bsSize="xs"
bsStyle="info" bsStyle="info"
onClick={() => { onClick={handleEnableStopOnFirstErrorClick}
startCompile({ stopOnFirstError: true })
setStopOnFirstError(true)
}}
/> />
), ),
'learn-more-link': ( 'learn-more-link': (

View file

@ -293,7 +293,10 @@ export function LocalCompileProvider({ children }) {
) )
// sample compile stats for real users // sample compile stats for real users
if (!window.user.alphaProgram && data.status === 'success') { if (
!window.user.alphaProgram &&
['success', 'stopped-on-first-error'].includes(data.status)
) {
sendMBSampled( sendMBSampled(
'compile-result', 'compile-result',
{ {
@ -301,6 +304,7 @@ export function LocalCompileProvider({ children }) {
warnings: result.logEntries.warnings.length, warnings: result.logEntries.warnings.length,
typesetting: result.logEntries.typesetting.length, typesetting: result.logEntries.typesetting.length,
newPdfPreview: true, // TODO: is this useful? newPdfPreview: true, // TODO: is this useful?
stopOnFirstError: data.options.stopOnFirstError,
}, },
0.01 0.01
) )

View file

@ -0,0 +1,34 @@
import { useCallback } from 'react'
import { useDetachCompileContext as useCompileContext } from '../context/detach-compile-context'
import { useProjectContext } from '../context/project-context'
import * as eventTracking from '../../infrastructure/event-tracking'
export function useStopOnFirstError(opts = {}) {
const { eventSource } = opts
const { stopOnFirstError, setStopOnFirstError } = useCompileContext()
const { _id: projectId } = useProjectContext()
const enableStopOnFirstError = useCallback(() => {
if (!stopOnFirstError) {
const opts = { projectId }
if (eventSource) {
opts.source = eventSource
}
eventTracking.sendMB('stop-on-first-error-enabled', opts)
}
setStopOnFirstError(true)
}, [eventSource, projectId, stopOnFirstError, setStopOnFirstError])
const disableStopOnFirstError = useCallback(() => {
const opts = { projectId }
if (eventSource) {
opts.source = eventSource
}
if (stopOnFirstError) {
eventTracking.sendMB('stop-on-first-error-disabled', opts)
}
setStopOnFirstError(false)
}, [eventSource, projectId, stopOnFirstError, setStopOnFirstError])
return { enableStopOnFirstError, disableStopOnFirstError }
}