Merge pull request #8297 from overleaf/em-halt-on-error-logs

Stop on first error info box in logs pane

GitOrigin-RevId: cf11f65d582d98bea93c6506393940d9a6144c0d
This commit is contained in:
Eric Mc Sween 2022-06-07 07:55:48 -04:00 committed by Copybot
parent 9a3f44e59a
commit cb657d1f1c
13 changed files with 119 additions and 36 deletions

View file

@ -64,16 +64,17 @@ function compile(req, res, next) {
'error running compile'
)
} else {
let file
status = 'failure'
for (file of outputFiles) {
if (file.path === 'output.pdf' && file.size > 0) {
status = 'success'
lastSuccessfulCompileTimestamp = Date.now()
}
}
if (status === 'failure') {
if (
outputFiles.some(
file => file.path === 'output.pdf' && file.size > 0
)
) {
status = 'success'
lastSuccessfulCompileTimestamp = Date.now()
} else if (request.stopOnFirstError) {
status = 'stopped-on-first-error'
} else {
status = 'failure'
logger.warn(
{ project_id: request.project_id, outputFiles },
'project failed to compile successfully, no output.pdf generated'
@ -81,13 +82,11 @@ function compile(req, res, next) {
}
// log an error if any core files are found
for (file of outputFiles) {
if (file.path === 'core') {
logger.error(
{ project_id: request.project_id, req, outputFiles },
'core file found in output'
)
}
if (outputFiles.some(file => file.path === 'core')) {
logger.error(
{ project_id: request.project_id, req, outputFiles },
'core file found in output'
)
}
}

View file

@ -89,6 +89,7 @@
"demonstrating_git_integration": "",
"department": "",
"description": "",
"disable_stop_on_first_error": "",
"dismiss": "",
"dismiss_error_popup": "",
"doesnt_match": "",
@ -252,7 +253,6 @@
"log_viewer_error": "",
"login_with_service": "",
"logs_and_output_files": "",
"logs_pane_info_message": "",
"main_file_not_found": "",
"make_email_primary_description": "",
"make_primary": "",
@ -435,6 +435,8 @@
"start_free_trial": "",
"stop_compile": "",
"stop_on_first_error": "",
"stop_on_first_error_enabled_description": "",
"stop_on_first_error_enabled_title": "",
"stop_on_validation_error": "",
"store_your_work": "",
"subject": "",

View file

@ -6,7 +6,8 @@ import Icon from '../../../shared/components/icon'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
function PdfHybridLogsButton() {
const { error, logEntries, toggleLogs, showLogs } = useCompileContext()
const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
useCompileContext()
const { t } = useTranslation()
@ -26,7 +27,7 @@ function PdfHybridLogsButton() {
>
<Button
bsStyle="link"
disabled={Boolean(error)}
disabled={Boolean(error || stoppedOnFirstError)}
active={showLogs}
className="toolbar-item log-btn"
onClick={handleClick}

View file

@ -72,8 +72,14 @@ PdfLogEntry.propTypes = {
logType: PropTypes.string,
formattedContent: PropTypes.node,
extraInfoURL: PropTypes.string,
level: PropTypes.oneOf(['error', 'warning', 'typesetting', 'raw', 'success'])
.isRequired,
level: PropTypes.oneOf([
'error',
'warning',
'info',
'typesetting',
'raw',
'success',
]).isRequired,
customClass: PropTypes.string,
showSourceLocationLink: PropTypes.bool,
showCloseButton: PropTypes.bool,

View file

@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'
import { memo } from 'react'
import classnames from 'classnames'
import PdfValidationIssue from './pdf-validation-issue'
import StopOnFirstErrorPrompt from './stop-on-first-error-prompt'
import TimeoutUpgradePrompt from './timeout-upgrade-prompt'
import PdfPreviewError from './pdf-preview-error'
import PdfClearCacheButton from './pdf-clear-cache-button'
@ -21,6 +22,8 @@ function PdfLogsViewer() {
rawLog,
validationIssues,
showLogs,
stopOnFirstError,
stoppedOnFirstError,
} = useCompileContext()
const { t } = useTranslation()
@ -30,6 +33,8 @@ function PdfLogsViewer() {
<div className="logs-pane-content">
{codeCheckFailed && <PdfCodeCheckFailedNotice />}
{stopOnFirstError && stoppedOnFirstError && <StopOnFirstErrorPrompt />}
{error && <PdfPreviewError error={error} />}
{error === 'timedout' && <TimeoutUpgradePrompt />}

View file

@ -0,0 +1,34 @@
import { useTranslation, Trans } from 'react-i18next'
import { Button } from 'react-bootstrap'
import PdfLogEntry from './pdf-log-entry'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
export default function StopOnFirstErrorPrompt() {
const { t } = useTranslation()
const { setStopOnFirstError, startCompile } = useCompileContext()
return (
<PdfLogEntry
headerTitle={t('stop_on_first_error_enabled_title')}
formattedContent={
<>
<Trans
i18nKey="stop_on_first_error_enabled_description"
// eslint-disable-next-line react/jsx-key
components={[<strong />]}
/>{' '}
<Button
bsSize="xs"
bsStyle="info"
onClick={() => {
startCompile({ stopOnFirstError: false })
setStopOnFirstError(false)
}}
>
{t('disable_stop_on_first_error')}
</Button>
</>
}
level="info"
/>
)
}

View file

@ -41,7 +41,10 @@ export default class DocumentCompiler {
this.currentDoc = null
this.error = undefined
this.timer = 0
this.stopOnFirstError = false
this.defaultOptions = {
draft: false,
stopOnFirstError: false,
}
this.debouncedAutoCompile = debounce(
() => {
@ -57,9 +60,7 @@ export default class DocumentCompiler {
// The main "compile" function.
// Call this directly to run a compile now, otherwise call debouncedAutoCompile.
async compile(options = {}) {
if (!options) {
options = {}
}
options = { ...this.defaultOptions, ...options }
// set "compiling" to true (in the React component's state), and return if it was already true
const wasCompiling = this.compilingRef.current
@ -85,14 +86,14 @@ export default class DocumentCompiler {
const body = {
rootDoc_id: this.getRootDocOverrideId(),
draft: this.draft,
draft: options.draft,
check: 'silent', // NOTE: 'error' and 'validate' are possible, but unused
// use incremental compile for all users but revert to a full compile
// if there was previously a server error
incrementalCompilesEnabled: !this.error,
}
if (getMeta('ol-showStopOnFirstError')) {
body.stopOnFirstError = this.stopOnFirstError
body.stopOnFirstError = options.stopOnFirstError
}
const data = await postJSON(
`/project/${this.projectId}/compile?${params}`,
@ -202,4 +203,8 @@ export default class DocumentCompiler {
this.setError('clear-cache')
})
}
setOption(option, value) {
this.defaultOptions[option] = value
}
}

View file

@ -32,6 +32,7 @@ function PreviewLogEntryHeader({
const logEntryHeaderClasses = classNames('log-entry-header', {
'log-entry-header-error': level === 'error',
'log-entry-header-warning': level === 'warning',
'log-entry-header-info': level === 'info',
'log-entry-header-typesetting': level === 'typesetting',
'log-entry-header-raw': level === 'raw',
'log-entry-header-success': level === 'success',
@ -41,6 +42,7 @@ function PreviewLogEntryHeader({
'log-entry-header-link-warning': level === 'warning',
'log-entry-header-link-typesetting': level === 'typesetting',
'log-entry-header-link-raw': level === 'raw',
'log-entry-header-link-info': level === 'info',
'log-entry-header-link-success': level === 'success',
})
const headerLogLocationTitle = t('navigate_log_source', {

View file

@ -79,7 +79,11 @@ export default class LatexParser {
}
currentLineIsError() {
return this.currentLine[0] === '!'
return (
this.currentLine[0] === '!' &&
this.currentLine !==
'! ==> Fatal error occurred, no output PDF file produced!'
)
}
currentLineIsFileLineError() {

View file

@ -50,6 +50,7 @@ export function DetachCompileProvider({ children }) {
showLogs: _showLogs,
stopOnFirstError: _stopOnFirstError,
stopOnValidationError: _stopOnValidationError,
stoppedOnFirstError: _stoppedOnFirstError,
uncompiled: _uncompiled,
validationIssues: _validationIssues,
firstRenderDone: _firstRenderDone,
@ -172,6 +173,12 @@ export function DetachCompileProvider({ children }) {
'detacher',
'detached'
)
const [stoppedOnFirstError] = useDetachStateWatcher(
'stoppedOnFirstError',
_stoppedOnFirstError,
'detacher',
'detached'
)
const [uncompiled] = useDetachStateWatcher(
'uncompiled',
_uncompiled,
@ -239,14 +246,12 @@ export function DetachCompileProvider({ children }) {
'detached',
'detacher'
)
const setStopOnFirstError = useDetachAction(
'setStopOnFirstError',
_setStopOnFirstError,
'detached',
'detacher'
)
const setStopOnValidationError = useDetachAction(
'setStopOnValidationError',
_setStopOnValidationError,
@ -333,6 +338,7 @@ export function DetachCompileProvider({ children }) {
stopCompile,
stopOnFirstError,
stopOnValidationError,
stoppedOnFirstError,
uncompiled,
validationIssues,
firstRenderDone,
@ -375,6 +381,7 @@ export function DetachCompileProvider({ children }) {
stopCompile,
stopOnFirstError,
stopOnValidationError,
stoppedOnFirstError,
uncompiled,
validationIssues,
firstRenderDone,

View file

@ -62,6 +62,7 @@ export const CompileContextPropTypes = {
showLogs: PropTypes.bool.isRequired,
stopOnFirstError: PropTypes.bool.isRequired,
stopOnValidationError: PropTypes.bool.isRequired,
stoppedOnFirstError: PropTypes.bool.isRequired,
uncompiled: PropTypes.bool,
validationIssues: PropTypes.object,
firstRenderDone: PropTypes.func,
@ -164,6 +165,9 @@ export function LocalCompileProvider({ children }) {
true
)
// whether the last compiles stopped on first error
const [stoppedOnFirstError, setStoppedOnFirstError] = useState(false)
// whether compiling should be prevented if there are linting errors
const [stopOnValidationError, setStopOnValidationError] = usePersistedState(
`stop_on_validation_error:${projectId}`,
@ -221,12 +225,12 @@ export function LocalCompileProvider({ children }) {
// keep draft setting in sync with the compiler
useEffect(() => {
compiler.draft = draft
compiler.setOption('draft', draft)
}, [compiler, draft])
// keep stop on first error setting in sync with the compiler
useEffect(() => {
compiler.stopOnFirstError = stopOnFirstError
compiler.setOption('stopOnFirstError', stopOnFirstError)
}, [compiler, stopOnFirstError])
// pass the "uncompiled" value up into the scope for use outside this context provider
@ -311,6 +315,11 @@ export function LocalCompileProvider({ children }) {
setShowLogs(false)
break
case 'stopped-on-first-error':
setError(undefined)
setShowLogs(true)
break
case 'clsi-maintenance':
case 'compile-in-progress':
case 'exited':
@ -354,6 +363,8 @@ export function LocalCompileProvider({ children }) {
setError('error')
break
}
setStoppedOnFirstError(data.status === 'stopped-on-first-error')
}
return () => {
@ -484,6 +495,7 @@ export function LocalCompileProvider({ children }) {
stopCompile,
stopOnFirstError,
stopOnValidationError,
stoppedOnFirstError,
uncompiled,
validationIssues,
firstRenderDone,
@ -523,6 +535,7 @@ export function LocalCompileProvider({ children }) {
stopCompile,
stopOnFirstError,
stopOnValidationError,
stoppedOnFirstError,
uncompiled,
validationIssues,
firstRenderDone,

View file

@ -68,11 +68,13 @@
.btn-alert-variant(@ol-blue);
}
.log-entry-header-raw {
.log-entry-header-raw,
.log-entry-header-info {
background-color: @ol-blue-gray-4;
}
.log-entry-header-link-raw {
.log-entry-header-link-raw,
.log-entry-header-link-info {
.btn-alert-variant(@ol-blue-gray-4);
}

View file

@ -1752,5 +1752,8 @@
"email_or_password_wrong_try_again_or_reset": "Your email or password is incorrect. Please try again, or <0>set or reset your password</0>.",
"compile_error_handling": "Compile Error Handling",
"stop_on_first_error": "Stop on first error",
"try_to_compile_despite_errors": "Try to compile despite errors"
"try_to_compile_despite_errors": "Try to compile despite errors",
"stop_on_first_error_enabled_title": "No PDF: Stop on first error enabled",
"stop_on_first_error_enabled_description": "<0>“Stop on first error” is enabled.</0> Disabling it may allow the compiler to produce a PDF (but your project will still have errors).",
"disable_stop_on_first_error": "Disable “Stop on first error”"
}