Просмотр исходного кода

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

[web] Expose pandoc errors in import

GitOrigin-RevId: 55f89b91a52099a99a5d955bc05f3657b87b2cdc
Mathias Jakobsen 2 месяцев назад
Родитель
Сommit
cabe0046c5

+ 5 - 2
services/web/app/src/Features/Uploads/ProjectUploadController.mjs

@@ -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) {

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 48 - 3
services/web/frontend/js/features/project-list/components/new-project-button/import-document-modal.tsx

@@ -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

+ 15 - 2
services/web/frontend/js/features/project-list/hooks/use-project-uploader.tsx

@@ -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 - 0
services/web/locales/en.json

@@ -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 doesn’t contain special characters, trailing/leading whitespace or more than __nameLimit__ characters",
+  "invalid_import_type": "Invalid import type",
   "invalid_institutional_email": "Your institution’s 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 institution’s domain. Please contact your IT department if you have any questions.",
   "invalid_organization_email": "Your organization’s 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, you’ll 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 couldn’t 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.",

+ 1 - 1
services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs

@@ -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()
           }