mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-22 02:04:31 +00:00
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:
parent
9a3f44e59a
commit
cb657d1f1c
13 changed files with 119 additions and 36 deletions
|
@ -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'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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": "",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 />}
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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”"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue