Merge pull request #34102 from overleaf/mj-document-import-errors

[web] Expose pandoc errors in import

GitOrigin-RevId: 55f89b91a52099a99a5d955bc05f3657b87b2cdc
This commit is contained in:
Mathias Jakobsen
2026-06-04 14:54:49 +01:00
committed by Copybot
parent 97247b8ea5
commit cabe0046c5
6 changed files with 72 additions and 8 deletions
@@ -188,7 +188,10 @@ async function importDocument(req, res, next) {
const { path } = req.file
const conversionType = req.query.type
if (!['docx', 'markdown'].includes(conversionType)) {
return res.status(400).json({ success: false, error: 'invalid_type' })
return res.status(400).json({
success: false,
error: req.i18n.translate('invalid_import_type'),
})
}
const name = Path.basename(req.body.name, Path.extname(req.body.name))
logger.debug({ path, userId, conversionType }, 'importing document file')
@@ -239,7 +242,7 @@ async function importDocument(req, res, next) {
) {
return res.status(422).json({
success: false,
error: 'file_too_large',
error: req.i18n.translate('file_too_large'),
})
}
if (error instanceof DocumentConversionError) {
@@ -2488,6 +2488,7 @@
"your_current_plan_gives_you": "",
"your_current_plan_supports_up_to_x_licenses": "",
"your_current_project_will_revert_to_the_version_from_time": "",
"your_document_couldnt_be_imported_check_our_guidance_or_expand_conversion_error_details": "",
"your_email_is_confirmed": "",
"your_feedback_matters_answer_two_quick_questions": "",
"your_git_access_info": "",
@@ -1,6 +1,6 @@
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import { Dashboard } from '@uppy/react'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { useProjectUploader } from '../../hooks/use-project-uploader'
import {
OLModal,
@@ -12,6 +12,7 @@ import {
import OLButton from '@/shared/components/ol/ol-button'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
import OLNotification from '@/shared/components/ol/ol-notification'
function ImportDocumentModal({
type,
@@ -23,6 +24,7 @@ function ImportDocumentModal({
openProject: (id: string, convertedFrom?: string) => void
}) {
const { t } = useTranslation()
const [error, setError] = useState<string | null>(null)
const IMPORT_CONFIGS = useMemo(
() => ({
docx: {
@@ -46,6 +48,18 @@ function ImportDocumentModal({
endpoint: `/project/new/import-document?type=${type}`,
allowedFileTypes: config.allowedFileTypes,
onSuccess: (projectId: string) => openProject(projectId, type),
onError: response => {
const message = response?.body?.error
if (message) {
setError(message)
}
},
onFileAdded: () => {
setError(null)
},
onFileRemoved: () => {
setError(null)
},
})
return (
@@ -56,11 +70,11 @@ function ImportDocumentModal({
id="upload-project-modal"
backdrop="static"
>
{/* TODO: make necessary changes here for import document modal */}
<OLModalHeader>
<OLModalTitle as="h3">{config.title}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
{error && <ErrorNotification message={error} />}
<p>{t('import_document_description')}</p>
<Dashboard
uppy={uppy}
@@ -87,4 +101,35 @@ function ImportDocumentModal({
)
}
const ErrorNotification = ({ message }: { message: string }) => {
const { t } = useTranslation()
return (
<OLNotification
type="error"
className="import-error-notification"
content={
<div>
<Trans
i18nKey="your_document_couldnt_be_imported_check_our_guidance_or_expand_conversion_error_details"
components={[
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
<a
href="https://docs.overleaf.com/managing-projects-and-files/importing-and-exporting-files#common-issues-and-how-to-address-them"
target="_BLANK"
rel="noopener noreferrer"
/>,
]}
/>
{message && (
<details style={{ maxHeight: '200px', overflow: 'auto' }}>
<summary>{t('conversion_error_details')}</summary>
<code style={{ wordBreak: 'break-all' }}>{message}</code>
</details>
)}
</div>
}
/>
)
}
export default ImportDocumentModal
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import Uppy from '@uppy/core'
import Uppy, { ErrorResponse } from '@uppy/core'
import XHRUpload from '@uppy/xhr-upload'
import getMeta from '@/utils/meta'
@@ -7,6 +7,9 @@ type UploaderConfig = {
endpoint: string
allowedFileTypes: string[]
onSuccess: (projectId: string) => void
onError?: (response: ErrorResponse | undefined) => void
onFileAdded?: () => void
onFileRemoved?: () => void
}
type ImportResponse = {
@@ -17,6 +20,9 @@ export function useProjectUploader({
endpoint,
allowedFileTypes,
onSuccess,
onError,
onFileAdded,
onFileRemoved,
}: UploaderConfig) {
const { maxUploadSize, projectUploadTimeout } = getMeta('ol-ExposedSettings')
const [ableToUpload, setAbleToUpload] = useState(false)
@@ -47,9 +53,10 @@ export function useProjectUploader({
// the rest of the files will appear on the 'restriction-failed' event callback
setAbleToUpload(true)
})
.on('upload-error', () => {
.on('upload-error', (_file, _err, response) => {
// refresh state so they can try uploading a new file
setAbleToUpload(false)
onError?.(response)
})
.on('upload-success', (_file, response) => {
const { project_id: projectId }: ImportResponse = response.body
@@ -70,6 +77,12 @@ export function useProjectUploader({
// reset state so they can try uploading a different file, etc
setAbleToUpload(false)
})
.on('file-added', () => {
onFileAdded?.()
})
.on('file-removed', () => {
onFileRemoved?.()
})
})
useEffect(() => {
+2
View File
@@ -1283,6 +1283,7 @@
"invalid_email": "An email address is invalid",
"invalid_file_name": "Invalid File Name",
"invalid_filename": "Upload failed: check that the file name doesnt contain special characters, trailing/leading whitespace or more than __nameLimit__ characters",
"invalid_import_type": "Invalid import type",
"invalid_institutional_email": "Your institutions SSO service returned your email address as __email__, which is an unexpected domain that we do not recognise as belonging to it. You may be able to change your primary email address via your user profile at your institution to one at your institutions domain. Please contact your IT department if you have any questions.",
"invalid_organization_email": "Your organizations SSO service returned your email address as <0>__institutionEmail__</0>, which is an unexpected domain that we do not recognise as belonging to it. Please <1>contact us</1> if you think this is in error.",
"invalid_password": "Invalid Password.",
@@ -3154,6 +3155,7 @@
"your_current_plan_gives_you": "By pausing your subscription, youll be able to access your premium features faster when you need them again.",
"your_current_plan_supports_up_to_x_licenses": "Your current plan supports up to __users__ licenses.",
"your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",
"your_document_couldnt_be_imported_check_our_guidance_or_expand_conversion_error_details": "Your document couldnt be imported. <0>Check our guidance on common import problems</0> or expand the conversion error details below.",
"your_email_is_confirmed": "Your email is confirmed.",
"your_email_is_not_set_up_with_sso": "Your email is not set up with __institutionName__ SSO. Please log in with your existing login method to confirm your identity.",
"your_feedback_matters_answer_two_quick_questions": "Your feedback matters! Answer two quick questions.",
@@ -636,7 +636,7 @@ describe('ProjectUploadController', function () {
ctx.res.json = data => {
expect(data).to.deep.equal({
success: false,
error: 'invalid_type',
error: 'invalid_import_type',
})
resolve()
}