Merge pull request #3242 from overleaf/jel-add-recompile-from-scratch

Add recompile from scratch option

GitOrigin-RevId: 59836df9049e307acb11824058024919409ea4a4
This commit is contained in:
Jessica Lawshe 2020-10-20 07:44:19 -05:00 committed by Copybot
parent 4690b2ec86
commit f8a8c9bbd6
8 changed files with 191 additions and 10 deletions

View file

@ -3,11 +3,13 @@ div.full-size.pdf(ng-controller="PdfController")
preview-pane( preview-pane(
compiler-state=`{ compiler-state=`{
isAutoCompileOn: autocompile_enabled, isAutoCompileOn: autocompile_enabled,
isClearingCache: pdf.clearingCache,
isCompiling: pdf.compiling, isCompiling: pdf.compiling,
isDraftModeOn: draft, isDraftModeOn: draft,
isSyntaxCheckOn: stop_on_validation_error, isSyntaxCheckOn: stop_on_validation_error,
logEntries: pdf.logEntries ? pdf.logEntries : {} logEntries: pdf.logEntries ? pdf.logEntries : {}
}` }`
on-clear-cache="clearCache"
on-recompile="recompile" on-recompile="recompile"
on-run-syntax-check-now="runSyntaxCheckNow" on-run-syntax-check-now="runSyntaxCheckNow"
on-set-auto-compile="setAutoCompile" on-set-auto-compile="setAutoCompile"
@ -379,6 +381,7 @@ script(type='text/ng-template', id='clearCacheModalTemplate')
.modal-body .modal-body
p #{translate("clear_cache_explanation")} p #{translate("clear_cache_explanation")}
p #{translate("clear_cache_is_safe")} p #{translate("clear_cache_is_safe")}
.alert.alert-danger(ng-if="state.error") #{translate("generic_something_went_wrong")}.
.modal-footer .modal-footer
button.btn.btn-default( button.btn.btn-default(
ng-click="cancel()" ng-click="cancel()"

View file

@ -29,5 +29,8 @@
"your_project_has_errors", "your_project_has_errors",
"view_warnings", "view_warnings",
"view_logs", "view_logs",
"view_pdf" "view_pdf",
"recompile_from_scratch",
"run_syntax_check_now",
"toggle_compile_options_menu"
] ]

View file

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
function PreviewPane({ function PreviewPane({
compilerState, compilerState,
onClearCache,
onRecompile, onRecompile,
onRunSyntaxCheckNow, onRunSyntaxCheckNow,
onSetAutoCompile, onSetAutoCompile,
@ -35,6 +36,7 @@ function PreviewPane({
compilerState={compilerState} compilerState={compilerState}
logsState={{ nErrors, nWarnings, nLogEntries }} logsState={{ nErrors, nWarnings, nLogEntries }}
showLogs={showLogs} showLogs={showLogs}
onClearCache={onClearCache}
onRecompile={onRecompile} onRecompile={onRecompile}
onRunSyntaxCheckNow={onRunSyntaxCheckNow} onRunSyntaxCheckNow={onRunSyntaxCheckNow}
onSetAutoCompile={onSetAutoCompile} onSetAutoCompile={onSetAutoCompile}
@ -67,6 +69,7 @@ PreviewPane.propTypes = {
isSyntaxCheckOn: PropTypes.bool.isRequired, isSyntaxCheckOn: PropTypes.bool.isRequired,
logEntries: PropTypes.object.isRequired logEntries: PropTypes.object.isRequired
}), }),
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired, onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired, onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired, onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -7,10 +7,12 @@ import Icon from '../../../shared/components/icon'
function PreviewRecompileButton({ function PreviewRecompileButton({
compilerState: { compilerState: {
isAutoCompileOn, isAutoCompileOn,
isClearingCache,
isCompiling, isCompiling,
isDraftModeOn, isDraftModeOn,
isSyntaxCheckOn isSyntaxCheckOn
}, },
onClearCache,
onRecompile, onRecompile,
onRunSyntaxCheckNow, onRunSyntaxCheckNow,
onSetAutoCompile, onSetAutoCompile,
@ -19,6 +21,16 @@ function PreviewRecompileButton({
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
function handleRecompileFromScratch() {
onClearCache()
.then(() => {
onRecompile()
})
.catch(error => {
console.error(error)
})
}
function handleSelectAutoCompileOn() { function handleSelectAutoCompileOn() {
onSetAutoCompile(true) onSetAutoCompile(true)
} }
@ -47,7 +59,7 @@ function PreviewRecompileButton({
<Dropdown id="pdf-recompile-dropdown" className="btn-recompile-group"> <Dropdown id="pdf-recompile-dropdown" className="btn-recompile-group">
<button className="btn btn-recompile" onClick={onRecompile}> <button className="btn btn-recompile" onClick={onRecompile}>
<Icon type="refresh" spin={isCompiling} /> <Icon type="refresh" spin={isCompiling} />
{isCompiling ? ( {isCompiling || isClearingCache ? (
<span className="btn-recompile-label"> <span className="btn-recompile-label">
{t('compiling')} {t('compiling')}
&hellip; &hellip;
@ -56,7 +68,10 @@ function PreviewRecompileButton({
<span className="btn-recompile-label">{t('recompile')}</span> <span className="btn-recompile-label">{t('recompile')}</span>
)} )}
</button> </button>
<Dropdown.Toggle className="btn btn-recompile" /> <Dropdown.Toggle
aria-label={t('toggle_compile_options_menu')}
className="btn btn-recompile"
/>
<Dropdown.Menu> <Dropdown.Menu>
<MenuItem header>{t('auto_compile')}</MenuItem> <MenuItem header>{t('auto_compile')}</MenuItem>
<MenuItem onSelect={handleSelectAutoCompileOn}> <MenuItem onSelect={handleSelectAutoCompileOn}>
@ -89,6 +104,14 @@ function PreviewRecompileButton({
<Icon type="" modifier="fw" /> <Icon type="" modifier="fw" />
{t('run_syntax_check_now')} {t('run_syntax_check_now')}
</MenuItem> </MenuItem>
<MenuItem divider />
<MenuItem
onSelect={handleRecompileFromScratch}
disabled={isCompiling || isClearingCache}
aria-disabled={!!(isCompiling || isClearingCache)}
>
{t('recompile_from_scratch')}
</MenuItem>
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
) )
@ -97,11 +120,13 @@ function PreviewRecompileButton({
PreviewRecompileButton.propTypes = { PreviewRecompileButton.propTypes = {
compilerState: PropTypes.shape({ compilerState: PropTypes.shape({
isAutoCompileOn: PropTypes.bool.isRequired, isAutoCompileOn: PropTypes.bool.isRequired,
isClearingCache: PropTypes.bool.isRequired,
isCompiling: PropTypes.bool.isRequired, isCompiling: PropTypes.bool.isRequired,
isDraftModeOn: PropTypes.bool.isRequired, isDraftModeOn: PropTypes.bool.isRequired,
isSyntaxCheckOn: PropTypes.bool.isRequired, isSyntaxCheckOn: PropTypes.bool.isRequired,
logEntries: PropTypes.object.isRequired logEntries: PropTypes.object.isRequired
}), }),
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired, onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired, onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired, onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -6,6 +6,7 @@ import PreviewLogsToggleButton from './preview-logs-toggle-button'
function PreviewToolbar({ function PreviewToolbar({
compilerState, compilerState,
logsState, logsState,
onClearCache,
onRecompile, onRecompile,
onRunSyntaxCheckNow, onRunSyntaxCheckNow,
onSetAutoCompile, onSetAutoCompile,
@ -24,6 +25,7 @@ function PreviewToolbar({
onSetAutoCompile={onSetAutoCompile} onSetAutoCompile={onSetAutoCompile}
onSetDraftMode={onSetDraftMode} onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck} onSetSyntaxCheck={onSetSyntaxCheck}
onClearCache={onClearCache}
/> />
</div> </div>
<div className="toolbar-pdf-right"> <div className="toolbar-pdf-right">
@ -51,6 +53,7 @@ PreviewToolbar.propTypes = {
nLogEntries: PropTypes.number.isRequired nLogEntries: PropTypes.number.isRequired
}), }),
showLogs: PropTypes.bool.isRequired, showLogs: PropTypes.bool.isRequired,
onClearCache: PropTypes.func.isRequired,
onRecompile: PropTypes.func.isRequired, onRecompile: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired, onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired, onSetAutoCompile: PropTypes.func.isRequired,

View file

@ -20,12 +20,14 @@ App.controller('PdfController', function(
$modal, $modal,
synctex, synctex,
eventTracking, eventTracking,
localStorage localStorage,
$q
) { ) {
let autoCompile = true let autoCompile = true
// pdf.view = uncompiled | pdf | errors // pdf.view = uncompiled | pdf | errors
$scope.pdf.view = $scope.pdf.url ? 'pdf' : 'uncompiled' $scope.pdf.view = $scope.pdf.url ? 'pdf' : 'uncompiled'
$scope.pdf.clearingCache = false
$scope.shouldShowLogs = false $scope.shouldShowLogs = false
$scope.wikiEnabled = window.wikiEnabled $scope.wikiEnabled = window.wikiEnabled
@ -796,7 +798,10 @@ App.controller('PdfController', function(
} }
$scope.clearCache = function() { $scope.clearCache = function() {
return $http({ $scope.pdf.clearingCache = true
const deferred = $q.defer()
$http({
url: `/project/${$scope.project_id}/output`, url: `/project/${$scope.project_id}/output`,
method: 'DELETE', method: 'DELETE',
params: { params: {
@ -806,6 +811,20 @@ App.controller('PdfController', function(
'X-Csrf-Token': window.csrfToken 'X-Csrf-Token': window.csrfToken
} }
}) })
.then(function(response) {
$scope.pdf.clearingCache = false
return deferred.resolve()
})
.catch(function(response) {
console.error(response)
const error = response.data
$scope.pdf.clearingCache = false
$scope.pdf.renderingError = false
$scope.pdf.error = true
$scope.pdf.view = 'errors'
return deferred.reject(error)
})
return deferred.promise
} }
$scope.toggleLogs = function() { $scope.toggleLogs = function() {
@ -1056,14 +1075,20 @@ App.controller('PdfLogEntryController', function($scope, ide, eventTracking) {
}) })
App.controller('ClearCacheModalController', function($scope, $modalInstance) { App.controller('ClearCacheModalController', function($scope, $modalInstance) {
$scope.state = { inflight: false } $scope.state = { error: false, inflight: false }
$scope.clear = function() { $scope.clear = function() {
$scope.state.inflight = true $scope.state.inflight = true
$scope.clearCache().then(function() { $scope
$scope.state.inflight = false .clearCache()
$modalInstance.close() .then(function() {
}) $scope.state.inflight = false
$modalInstance.close()
})
.catch(function() {
$scope.state.error = true
$scope.state.inflight = false
})
} }
$scope.cancel = () => $modalInstance.dismiss('cancel') $scope.cancel = () => $modalInstance.dismiss('cancel')

View file

@ -3,6 +3,7 @@
"n_warnings_plural": "__count__ warnings", "n_warnings_plural": "__count__ warnings",
"n_errors": "__count__ error", "n_errors": "__count__ error",
"n_errors_plural": "__count__ errors", "n_errors_plural": "__count__ errors",
"toggle_compile_options_menu": "Toggle compile options menu",
"view_pdf": "View PDF", "view_pdf": "View PDF",
"your_project_has_errors": "Your project has errors", "your_project_has_errors": "Your project has errors",
"view_warnings": "View warnings", "view_warnings": "View warnings",

View file

@ -0,0 +1,118 @@
import React from 'react'
import sinon from 'sinon'
import { screen, render, fireEvent } from '@testing-library/react'
import PreviewRecompileButton from '../../../../../frontend/js/features/preview/components/preview-recompile-button'
const { expect } = require('chai')
describe('<PreviewRecompileButton />', function() {
let onRecompile, onClearCache
beforeEach(function() {
onRecompile = sinon.stub().resolves()
onClearCache = sinon.stub().resolves()
})
it('renders all items', function() {
renderPreviewRecompileButton()
const menuItems = screen.getAllByRole('menuitem')
expect(menuItems.length).to.equal(8)
expect(menuItems.map(item => item.textContent)).to.deep.equal([
'On',
'Off',
'Normal',
'Fast [draft]',
'Check syntax before compile',
"Don't check syntax",
'Run syntax check now',
'Recompile from scratch'
])
const menuHeadingItems = screen.getAllByRole('heading')
expect(menuHeadingItems.length).to.equal(3)
expect(menuHeadingItems.map(item => item.textContent)).to.deep.equal([
'Auto Compile',
'Compile Mode',
'Syntax Checks'
])
})
describe('Recompile from scratch', function() {
describe('click', function() {
it('should call onClearCache and onRecompile', async function() {
renderPreviewRecompileButton()
const button = screen.getByRole('menuitem', {
name: 'Recompile from scratch'
})
await fireEvent.click(button)
expect(onClearCache).to.have.been.calledOnce
expect(onRecompile).to.have.been.calledOnce
})
})
describe('processing', function() {
it('shows processing view and disable menuItem when clearing cache', function() {
renderPreviewRecompileButton({ isClearingCache: true })
screen.getByRole('button', { name: 'Compiling …' })
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.getAttribute('aria-disabled')
).to.equal('true')
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.closest('li')
.getAttribute('class')
).to.equal('disabled')
})
it('shows processing view and disable menuItem when recompiling', function() {
renderPreviewRecompileButton({ isCompiling: true })
screen.getByRole('button', { name: 'Compiling …' })
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.getAttribute('aria-disabled')
).to.equal('true')
expect(
screen
.getByRole('menuitem', {
name: 'Recompile from scratch'
})
.closest('li')
.getAttribute('class')
).to.equal('disabled')
})
})
})
function renderPreviewRecompileButton(compilerState = {}) {
render(
<PreviewRecompileButton
compilerState={{
isAutoCompileOn: true,
isClearingCache: false,
isCompiling: false,
isDraftModeOn: false,
isSyntaxCheckOn: false,
...compilerState
}}
onRecompile={onRecompile}
onRunSyntaxCheckNow={() => {}}
onSetAutoCompile={() => {}}
onSetDraftMode={() => {}}
onSetSyntaxCheck={() => {}}
onClearCache={onClearCache}
/>
)
}
})