diff --git a/services/web/app/src/Features/Compile/CompileManager.mjs b/services/web/app/src/Features/Compile/CompileManager.mjs index 9a4ac41946..e983c4d514 100644 --- a/services/web/app/src/Features/Compile/CompileManager.mjs +++ b/services/web/app/src/Features/Compile/CompileManager.mjs @@ -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 { diff --git a/services/web/app/src/Features/PublishedPresentation/PublishedPresentationController.mjs b/services/web/app/src/Features/PublishedPresentation/PublishedPresentationController.mjs index f25ba3bc96..8df698cd8d 100644 --- a/services/web/app/src/Features/PublishedPresentation/PublishedPresentationController.mjs +++ b/services/web/app/src/Features/PublishedPresentation/PublishedPresentationController.mjs @@ -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) { diff --git a/services/web/app/src/Features/PublishedPresentation/PublishedPresentationManager.mjs b/services/web/app/src/Features/PublishedPresentation/PublishedPresentationManager.mjs index 9daa9575c2..3068899bf1 100644 --- a/services/web/app/src/Features/PublishedPresentation/PublishedPresentationManager.mjs +++ b/services/web/app/src/Features/PublishedPresentation/PublishedPresentationManager.mjs @@ -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. diff --git a/services/web/frontend/js/features/share-project-modal/components/publish-presentation-section.tsx b/services/web/frontend/js/features/share-project-modal/components/publish-presentation-section.tsx index 01e0ed43e0..d7ad420cdd 100644 --- a/services/web/frontend/js/features/share-project-modal/components/publish-presentation-section.tsx +++ b/services/web/frontend/js/features/share-project-modal/components/publish-presentation-section.tsx @@ -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({ published: false }) const [visibility, setVisibility] = useState('private') const [loading, setLoading] = useState(false) - const [error, setError] = useState(false) + const [error, setError] = useState(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() {
)}