Преглед изворни кода

Merge pull request #7169 from mizozobu/imprv/error-handling

imprv: error handling
Syunsuke Komma пре 3 година
родитељ
комит
72973a29be

+ 0 - 1
packages/app/public/static/locales/en_US/admin.json

@@ -1032,7 +1032,6 @@
   },
   "g2g": {
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
-    "error_upload_attachment": "Failed to upload attachment <code>%s</code>",
     "error_generate_growi_archive": "Failed to generate GROWI archive file",
     "error_send_growi_archive": "Failed to send GROWI archive file to new GROWI"
   },

+ 0 - 1
packages/app/public/static/locales/ja_JP/admin.json

@@ -1027,7 +1027,6 @@
   },
   "g2g": {
     "transfer_success": "G2G移行が完了しました",
-    "error_upload_attachment": "添付ファイルのアップロードに失敗しました",
     "error_generate_growi_archive": "GROWI アーカイブファイルの作成に失敗しました",
     "error_send_growi_archive": "GROWI アーカイブファイルの送信に失敗しました"
   },

+ 0 - 1
packages/app/public/static/locales/zh_CN/admin.json

@@ -1027,7 +1027,6 @@
   },
   "g2g": {
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
-    "error_upload_attachment": "Failed to upload attachment <code>%s</code>",
     "error_generate_growi_archive": "Failed to generate GROWI archive file",
     "error_send_growi_archive": "Failed to send GROWI archive file to new GROWI"
   },

+ 7 - 15
packages/app/src/client/services/g2g-transfer.ts

@@ -1,23 +1,15 @@
-import { useMemo, useState } from 'react';
-
-import { throttle } from 'throttle-debounce';
+import { useCallback, useState } from 'react';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 
-// eslint-disable-next-line @typescript-eslint/ban-types
-export const useGenerateTransferKeyWithThrottle = (): {transferKey: string, generateTransferKeyWithThrottle: Function} => {
+export const useGenerateTransferKey = (): {transferKey: string, generateTransferKey: () => Promise<void>} => {
   const [transferKey, setTransferKey] = useState('');
 
-  const generateTransferKeyWithThrottle = useMemo(() => {
-    const href = document.location.href;
-    const url = new URL(href);
-
-    return throttle(1000, async() => {
-      const response = await apiv3Post('/g2g-transfer/generate-key', { appSiteUrl: url.origin });
-      const { transferKey } = response.data;
-      setTransferKey(transferKey);
-    });
+  const generateTransferKey = useCallback(async() => {
+    const response = await apiv3Post('/g2g-transfer/generate-key', { appSiteUrl: window.location.origin });
+    const { transferKey } = response.data;
+    setTransferKey(transferKey);
   }, []);
 
-  return { transferKey, generateTransferKeyWithThrottle };
+  return { transferKey, generateTransferKey };
 };

+ 10 - 5
packages/app/src/components/Admin/G2GDataTransfer.tsx

@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
-import { useGenerateTransferKeyWithThrottle } from '~/client/services/g2g-transfer';
+import { useGenerateTransferKey } from '~/client/services/g2g-transfer';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
 import { G2G_PROGRESS_STATUS, type G2GProgress } from '~/interfaces/g2g-transfer';
@@ -80,11 +80,16 @@ const G2GDataTransfer = (): JSX.Element => {
     }
   }, [socket]);
 
-  const { transferKey, generateTransferKeyWithThrottle } = useGenerateTransferKeyWithThrottle();
+  const { transferKey, generateTransferKey } = useGenerateTransferKey();
 
-  const onClickHandler = useCallback(() => {
-    generateTransferKeyWithThrottle();
-  }, [generateTransferKeyWithThrottle]);
+  const onClickHandler = useCallback(async() => {
+    try {
+      await generateTransferKey();
+    }
+    catch (errs) {
+      toastError(errs);
+    }
+  }, [generateTransferKey]);
 
   const startTransfer = useCallback(async(e) => {
     e.preventDefault();

+ 7 - 8
packages/app/src/components/Admin/G2GDataTransferExportForm.tsx

@@ -101,7 +101,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
           const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName);
 
           if (optionsMap[collectionName] == null) {
-            return <></>;
+            return null;
           }
 
           return (
@@ -112,7 +112,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
                 // insertedCount={collectionProgress ? collectionProgress.insertedCount : 0}
                 // modifiedCount={collectionProgress ? collectionProgress.modifiedCount : 0}
                 // errorsCount={errors ? errors.length : 0}
-                isImporting={0}
+                isImporting={false}
                 isImported={false}
                 insertedCount={0}
                 modifiedCount={0}
@@ -135,17 +135,16 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     );
   };
 
-  const WarnForGroups = ({ errors, key }): JSX.Element => {
+  const WarnForGroups = ({ errors }): JSX.Element => {
     if (errors.length === 0) {
       return <></>;
     }
 
     return (
-      <div key={key} className="alert alert-warning">
+      <div className="alert alert-warning">
         <ul>
-          {errors.map((error, index) => {
-            // eslint-disable-next-line react/no-array-index-key
-            return <li key={`${key}-${index}`}>{error}</li>;
+          {errors.map((error, i) => {
+            return <li key={i}>{error}</li>;
           })}
         </ul>
       </div>
@@ -165,7 +164,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
       <div className="mt-4">
         <legend>{groupName} Collections</legend>
         <ImportItems collectionNames={collectionNames} />
-        <WarnForGroups errors={errors} key={`warnFor${groupName}`} />
+        <WarnForGroups errors={errors} />
       </div>
     );
   };

+ 4 - 10
packages/app/src/components/DataTransferForm.tsx

@@ -1,20 +1,14 @@
-import React, { useCallback } from 'react';
-
+import React from 'react';
 
 import { useTranslation } from 'react-i18next';
 
-import { useGenerateTransferKeyWithThrottle } from '~/client/services/g2g-transfer';
+import { useGenerateTransferKey } from '~/client/services/g2g-transfer';
 
 import CustomCopyToClipBoard from './Common/CustomCopyToClipBoard';
 
 const DataTransferForm = (): JSX.Element => {
   const { t } = useTranslation();
-
-  const { transferKey, generateTransferKeyWithThrottle } = useGenerateTransferKeyWithThrottle();
-
-  const onClickHandler = useCallback(() => {
-    generateTransferKeyWithThrottle();
-  }, [generateTransferKeyWithThrottle]);
+  const { transferKey, generateTransferKey } = useGenerateTransferKey();
 
   return (
     <div data-testid="installerForm" className="p-3">
@@ -24,7 +18,7 @@ const DataTransferForm = (): JSX.Element => {
 
       <div className="form-group row mt-3">
         <div className="col-md-12">
-          <button type="button" className="btn btn-primary w-100" onClick={onClickHandler}>
+          <button type="button" className="btn btn-primary w-100" onClick={generateTransferKey}>
             {t('g2g_data_transfer.publish_transfer_key')}
           </button>
         </div>

+ 5 - 15
packages/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -131,7 +131,7 @@ module.exports = (crowi: Crowi): Router => {
 
   // Auto import
   // eslint-disable-next-line max-len
-  receiveRouter.post('/', uploads.single('transferDataZipFile'), validateTransferKey, async(req: Request, res: ApiV3Response) => {
+  receiveRouter.post('/', uploads.single('transferDataZipFile'), validateTransferKey, async(req: Request & { file: any; }, res: ApiV3Response) => {
     const { file } = req;
     const {
       collections: strCollections,
@@ -216,7 +216,7 @@ module.exports = (crowi: Crowi): Router => {
 
   // This endpoint uses multer's MemoryStorage since the received data should be persisted directly on attachment storage.
   receiveRouter.post('/attachment', uploadsForAttachment.single('content'), validateTransferKey,
-    async(req: Request, res: ApiV3Response) => {
+    async(req: Request & { file: any; }, res: ApiV3Response) => {
       const { file } = req;
       const { attachmentMetadata } = req.body;
 
@@ -314,23 +314,13 @@ module.exports = (crowi: Crowi): Router => {
     // Check if can transfer
     const transferability = await g2gTransferPusherService.getTransferability(toGROWIInfo);
     if (!transferability.canTransfer) {
-      logger.debug('Could not transfer.');
       return res.apiv3Err(new ErrorV3(transferability.reason, 'growi_incompatible_to_transfer'));
     }
 
     // Start transfer
-    try {
-      await g2gTransferPusherService.startTransfer(tk, req.user, collections, optionsMap);
-    }
-    catch (err) {
-      logger.error(err);
-
-      if (!isG2GTransferError(err)) {
-        return res.apiv3Err(new ErrorV3('Failed to transfer', 'failed_to_transfer'), 500);
-      }
-
-      return res.apiv3Err(new ErrorV3(err.message, err.code), 500);
-    }
+    // DO NOT "await". Let it run in the background.
+    // Errors should be emitted through websocket.
+    g2gTransferPusherService.startTransfer(tk, req.user, collections, optionsMap);
 
     return res.apiv3({ message: 'Successfully requested auto transfer.' });
   });

+ 14 - 1
packages/app/src/server/service/g2g-transfer.ts

@@ -298,7 +298,8 @@ export class G2GTransferPusherService implements Pusher {
 
   public async transferAttachments(tk: TransferKey): Promise<void> {
     const BATCH_SIZE = 100;
-    const { fileUploadService } = this.crowi;
+    const { fileUploadService, socketIoService } = this.crowi;
+    const socket = socketIoService.getAdminSocket();
     const Attachment = this.crowi.model('Attachment');
     const filesFromNewGrowi = await this.listFilesInStorage(tk);
 
@@ -364,6 +365,12 @@ export class G2GTransferPusherService implements Pusher {
         }
         catch (err) {
           logger.warn(`Error occured when getting Attachment(ID=${attachment.id}), skipping: `, err);
+          socket.emit('admin:g2gError', {
+            message: `Error occured when uploading Attachment(ID=${attachment.id})`,
+            key: `Error occured when uploading Attachment(ID=${attachment.id})`,
+            // TODO: emit error with params
+            // key: 'admin:g2g:error_upload_attachment',
+          });
           continue;
         }
         // post each attachment file data to receiver
@@ -372,6 +379,12 @@ export class G2GTransferPusherService implements Pusher {
         }
         catch (err) {
           logger.error(`Error occured when uploading attachment(ID=${attachment.id})`, err);
+          socket.emit('admin:g2gError', {
+            message: `Error occured when uploading Attachment(ID=${attachment.id})`,
+            key: `Error occured when uploading Attachment(ID=${attachment.id})`,
+            // TODO: emit error with params
+            // key: 'admin:g2g:error_upload_attachment',
+          });
         }
       }
     }