Fix publish-presentation failing right after an editor compile
Build and Deploy Verso / deploy (push) Successful in 7m49s
Build and Deploy Verso / deploy (push) Successful in 7m49s
CompileManager.compile debounces compiles via a Redis key set on every compile
(_checkIfRecentlyCompiled), returning {status:'too-recently-compiled',
outputFiles:[]} when the editor has just auto-compiled. Publishing called
compile() and then required output.html, so it threw "did not produce an HTML
presentation" — which is why Preview/Publish errored whenever the deck was
freshly compiled.
- CompileManager.compile: honour options.bypassRecentCompileCheck to skip the
debounce (still runs the normal autocompile-limit guards).
- PublishedPresentationManager: publish with bypassRecentCompileCheck, and put
the compile status in the error message for diagnosis.
- Controller: catch publish errors, log them, and return the message so the
Share dialog can show what went wrong instead of a generic error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,12 +28,17 @@ function generateBuildId() {
|
||||
}
|
||||
|
||||
async function compile(projectId, userId, options = {}) {
|
||||
const recentlyCompiled = await CompileManager._checkIfRecentlyCompiled(
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
if (recentlyCompiled) {
|
||||
return { status: 'too-recently-compiled', outputFiles: [] }
|
||||
// Publishing a presentation needs a full output set on demand, so it can ask
|
||||
// to skip the debounce that suppresses compiles right after the editor's
|
||||
// auto-compile (which would otherwise return no output files).
|
||||
if (!options.bypassRecentCompileCheck) {
|
||||
const recentlyCompiled = await CompileManager._checkIfRecentlyCompiled(
|
||||
projectId,
|
||||
userId
|
||||
)
|
||||
if (recentlyCompiled) {
|
||||
return { status: 'too-recently-compiled', outputFiles: [] }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+15
-7
@@ -23,14 +23,22 @@ function _serialize(record) {
|
||||
async function publish(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const requested = req.body?.visibility
|
||||
const visibility =
|
||||
req.body?.visibility === 'public' ? 'public' : req.body?.visibility
|
||||
const record = await PublishedPresentationManager.promises.publish(
|
||||
projectId,
|
||||
userId,
|
||||
{ visibility: visibility === 'public' || visibility === 'private' ? visibility : undefined }
|
||||
)
|
||||
res.json(_serialize(record))
|
||||
requested === 'public' || requested === 'private' ? requested : undefined
|
||||
try {
|
||||
const record = await PublishedPresentationManager.promises.publish(
|
||||
projectId,
|
||||
userId,
|
||||
{ visibility }
|
||||
)
|
||||
res.json(_serialize(record))
|
||||
} catch (err) {
|
||||
logger.error({ err, projectId }, 'failed to publish presentation')
|
||||
res
|
||||
.status(400)
|
||||
.json({ message: err.message || 'failed to publish presentation' })
|
||||
}
|
||||
}
|
||||
|
||||
async function status(req, res) {
|
||||
|
||||
+9
-4
@@ -55,14 +55,19 @@ async function _downloadOutputFile(
|
||||
// omitted, an existing record keeps its visibility and a new one defaults to
|
||||
// 'private'.
|
||||
async function publish(projectId, userId, { visibility } = {}) {
|
||||
const { outputFiles, clsiServerId, buildId } =
|
||||
await CompileManager.promises.compile(projectId, userId, {})
|
||||
const { status, outputFiles, clsiServerId, buildId } =
|
||||
await CompileManager.promises.compile(projectId, userId, {
|
||||
bypassRecentCompileCheck: true,
|
||||
})
|
||||
|
||||
if (!buildId || !outputFiles?.some(f => f.path === 'output.html')) {
|
||||
if (!outputFiles?.some(f => f.path === 'output.html')) {
|
||||
throw new Errors.InvalidError(
|
||||
'project did not produce an HTML presentation'
|
||||
`project did not produce an HTML presentation (compile status: ${status})`
|
||||
)
|
||||
}
|
||||
if (!buildId) {
|
||||
throw new Errors.InvalidError('compile produced no build id')
|
||||
}
|
||||
|
||||
// Output files are stored per-user unless per-user compiles are disabled;
|
||||
// mirror CompileManager so the download URLs resolve to the right location.
|
||||
|
||||
+14
-7
@@ -1,7 +1,12 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { getJSON, postJSON, deleteJSON } from '@/infrastructure/fetch-json'
|
||||
import {
|
||||
getJSON,
|
||||
postJSON,
|
||||
deleteJSON,
|
||||
getUserFacingMessage,
|
||||
} from '@/infrastructure/fetch-json'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
@@ -26,7 +31,7 @@ export default function PublishPresentationSection() {
|
||||
const [state, setState] = useState<PublishState>({ published: false })
|
||||
const [visibility, setVisibility] = useState<Visibility>('private')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,21 +46,21 @@ export default function PublishPresentationSection() {
|
||||
|
||||
const publish = useCallback(() => {
|
||||
setLoading(true)
|
||||
setError(false)
|
||||
setError(null)
|
||||
postJSON(`/project/${projectId}/publish-presentation`, {
|
||||
body: { visibility },
|
||||
})
|
||||
.then((data: PublishState) => setState(data))
|
||||
.catch(() => setError(true))
|
||||
.catch(err => setError(getUserFacingMessage(err) ?? 'error'))
|
||||
.finally(() => setLoading(false))
|
||||
}, [projectId, visibility])
|
||||
|
||||
const unpublish = useCallback(() => {
|
||||
setLoading(true)
|
||||
setError(false)
|
||||
setError(null)
|
||||
deleteJSON(`/project/${projectId}/publish-presentation`)
|
||||
.then(() => setState({ published: false }))
|
||||
.catch(() => setError(true))
|
||||
.catch(err => setError(getUserFacingMessage(err) ?? 'error'))
|
||||
.finally(() => setLoading(false))
|
||||
}, [projectId])
|
||||
|
||||
@@ -132,7 +137,9 @@ export default function PublishPresentationSection() {
|
||||
<div className="mt-2">
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={t('generic_something_went_wrong')}
|
||||
content={
|
||||
error === 'error' ? t('generic_something_went_wrong') : error
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user