Răsfoiți Sursa

Merge pull request #2833 from weseek/feat/adding-messages-on-dataimport-page

Feat/adding messages on dataimport page
Yuki Takei 5 ani în urmă
părinte
comite
14b30e04bb

+ 5 - 0
resource/locales/en_US/admin/admin.json

@@ -148,6 +148,7 @@
       "upload": "Upload",
       "upload": "Upload",
       "discard": "Discard uploaded data",
       "discard": "Discard uploaded data",
       "errors": {
       "errors": {
+        "different_versions": "this growi and the uploarded data versions are not met",
         "at_least_one": "Select one or more collections.",
         "at_least_one": "Select one or more collections.",
         "page_and_revision": "'Pages' and 'Revisions' must be imported both.",
         "page_and_revision": "'Pages' and 'Revisions' must be imported both.",
         "depends": "'{{target}}' must be selected when '{{condition}}' is selected."
         "depends": "'{{target}}' must be selected when '{{condition}}' is selected."
@@ -190,6 +191,10 @@
       "test_connection": "Test connection to qiita:team"
       "test_connection": "Test connection to qiita:team"
     },
     },
     "import": "Import",
     "import": "Import",
+    "skip_username_and_email_when_overlapped": "Skip username and email using same username and email in new environment",
+    "prepare_new_account_for_migration":"Prepare new account for migration",
+    "archive_data_import_detail":"More Details? Ckick here.",
+    "admin_archive_data_import_guide_url":"https://docs.growi.org/en/admin-guide/management-cookbook/import.html",
     "page_skip": "Pages with a name that already exists on GROWI are not imported",
     "page_skip": "Pages with a name that already exists on GROWI are not imported",
     "Directory_hierarchy_tag": "Directory hierarchy tag"
     "Directory_hierarchy_tag": "Directory hierarchy tag"
   },
   },

+ 5 - 0
resource/locales/ja_JP/admin/admin.json

@@ -166,6 +166,7 @@
       "upload": "アップロード",
       "upload": "アップロード",
       "discard": "アップロードしたデータを破棄する",
       "discard": "アップロードしたデータを破棄する",
       "errors": {
       "errors": {
+        "different_versions": "現在のGROWIとアップロードしたデータのバージョンが違います",
         "at_least_one": "コレクションが選択されていません",
         "at_least_one": "コレクションが選択されていません",
         "page_and_revision": "'Pages' と 'Revisions' はセットでインポートする必要があります",
         "page_and_revision": "'Pages' と 'Revisions' はセットでインポートする必要があります",
         "depends": "'{{condition}}' をインポートする場合は、'{{target}}' を一緒に選択する必要があります"
         "depends": "'{{condition}}' をインポートする場合は、'{{target}}' を一緒に選択する必要があります"
@@ -208,6 +209,10 @@
       "test_connection": "接続テスト"
       "test_connection": "接続テスト"
     },
     },
     "import": "インポート",
     "import": "インポート",
+    "skip_username_and_email_when_overlapped": "ユーザー名またはメールアドレスが同じ場合、その部分がスキップされます。",
+    "prepare_new_account_for_migration":"移行用のアカウントを新環境で用意してください。",
+    "archive_data_import_detail":"参考: GROWI Docs - データのインポート",
+    "admin_archive_data_import_guide_url":"https://docs.growi.org/ja/admin-guide/management-cookbook/import.html#growi-%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96%E3%83%87%E3%83%BC%E3%82%BF%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88",
     "page_skip": "既に GROWI 側に同名のページが存在する場合、そのページはスキップされます",
     "page_skip": "既に GROWI 側に同名のページが存在する場合、そのページはスキップされます",
     "Directory_hierarchy_tag": "ディレクトリ階層タグ"
     "Directory_hierarchy_tag": "ディレクトリ階層タグ"
   },
   },

+ 6 - 1
resource/locales/zh_CN/admin/admin.json

@@ -160,6 +160,7 @@
 			"upload": "Upload",
 			"upload": "Upload",
 			"discard": "Discard uploaded data",
 			"discard": "Discard uploaded data",
 			"errors": {
 			"errors": {
+        "versions_not_met": "this growi and the uploarded data versions are not met",
 				"at_least_one": "Select one or more collections.",
 				"at_least_one": "Select one or more collections.",
 				"page_and_revision": "'Pages' and 'Revisions' must be imported both.",
 				"page_and_revision": "'Pages' and 'Revisions' must be imported both.",
 				"depends": "'{{target}}' must be selected when '{{condition}}' is selected."
 				"depends": "'{{target}}' must be selected when '{{condition}}' is selected."
@@ -201,7 +202,11 @@
 			"access_token": "Access token",
 			"access_token": "Access token",
 			"test_connection": "Test connection to qiita:team"
 			"test_connection": "Test connection to qiita:team"
 		},
 		},
-		"import": "Import",
+    "import": "Import",
+    "skip_username_and_email_when_overlapped": "Skip username and email using same username and email in new environment",
+    "prepare_new_account_for_migration":"Prepare new account for migration",
+    "archive_data_import_detail":"More details? Click here.",
+    "admin_archive_data_import_guide_url":"https://docs.growi.org/en/admin-guide/management-cookbook/import.html",
 		"page_skip": "Pages with a name that already exists on GROWI are not imported",
 		"page_skip": "Pages with a name that already exists on GROWI are not imported",
 		"Directory_hierarchy_tag": "Directory hierarchy tag"
 		"Directory_hierarchy_tag": "Directory hierarchy tag"
 	},
 	},

+ 10 - 1
src/client/js/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx

@@ -58,7 +58,16 @@ class RebuildIndexControls extends React.Component {
       return null;
       return null;
     }
     }
 
 
-    const header = isRebuildingCompleted ? 'Completed' : `Processing.. (${skip} skips)`;
+    function getCompletedLabel() {
+      const completedLabel = skip === 0 ? 'Completed' : `Done (${skip} skips)`;
+      return completedLabel;
+    }
+
+    function getSkipLabel() {
+      return `Processing.. (${skip} skips)`;
+    }
+
+    const header = isRebuildingCompleted ? getCompletedLabel() : getSkipLabel();
 
 
     return (
     return (
       <ProgressBar
       <ProgressBar

+ 18 - 4
src/client/js/components/Admin/ImportData/GrowiArchive/UploadForm.jsx

@@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next';
 
 
 import { withUnstatedContainers } from '../../../UnstatedUtils';
 import { withUnstatedContainers } from '../../../UnstatedUtils';
 import AppContainer from '../../../../services/AppContainer';
 import AppContainer from '../../../../services/AppContainer';
-// import { toastSuccess, toastError } from '../../../util/apiNotification';
+import { toastError } from '../../../../util/apiNotification';
 
 
 class UploadForm extends React.Component {
 class UploadForm extends React.Component {
 
 
@@ -31,9 +31,21 @@ class UploadForm extends React.Component {
     formData.append('_csrf', this.props.appContainer.csrfToken);
     formData.append('_csrf', this.props.appContainer.csrfToken);
     formData.append('file', this.inputRef.current.files[0]);
     formData.append('file', this.inputRef.current.files[0]);
 
 
-    const { data } = await this.props.appContainer.apiv3Post('/import/upload', formData);
-    this.props.onUpload(data);
-    // TODO: toastSuccess, toastError
+    try {
+      const { data } = await this.props.appContainer.apiv3Post('/import/upload', formData);
+      // TODO: toastSuccess, toastError
+      this.props.onUpload(data);
+    }
+    catch (err) {
+      if (err[0].code === 'versions-are-not-met') {
+        if (this.props.onVersionMismatch !== null) {
+          this.props.onVersionMismatch(err[0].code);
+        }
+      }
+      else {
+        toastError(err);
+      }
+    }
   }
   }
 
 
   validateForm() {
   validateForm() {
@@ -83,6 +95,8 @@ UploadForm.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   onUpload: PropTypes.func.isRequired,
   onUpload: PropTypes.func.isRequired,
+  isTheSameVersion: PropTypes.bool,
+  onVersionMismatch: PropTypes.func,
 };
 };
 
 
 /**
 /**

+ 50 - 8
src/client/js/components/Admin/ImportData/GrowiArchiveSection.jsx

@@ -18,6 +18,7 @@ class GrowiArchiveSection extends React.Component {
     this.initialState = {
     this.initialState = {
       fileName: null,
       fileName: null,
       innerFileStats: null,
       innerFileStats: null,
+      isTheSameVersion: null,
     };
     };
 
 
     this.state = this.initialState;
     this.state = this.initialState;
@@ -25,6 +26,8 @@ class GrowiArchiveSection extends React.Component {
     this.handleUpload = this.handleUpload.bind(this);
     this.handleUpload = this.handleUpload.bind(this);
     this.discardData = this.discardData.bind(this);
     this.discardData = this.discardData.bind(this);
     this.resetState = this.resetState.bind(this);
     this.resetState = this.resetState.bind(this);
+    this.handleMismatchedVersions = this.handleMismatchedVersions.bind(this);
+    this.renderDefferentVersionAlert = this.renderDefferentVersionAlert.bind(this);
   }
   }
 
 
   async componentWillMount() {
   async componentWillMount() {
@@ -33,14 +36,19 @@ class GrowiArchiveSection extends React.Component {
 
 
     if (res.data.zipFileStat != null) {
     if (res.data.zipFileStat != null) {
       const { fileName, innerFileStats } = res.data.zipFileStat;
       const { fileName, innerFileStats } = res.data.zipFileStat;
-      this.setState({ fileName, innerFileStats });
+      const { isTheSameVersion } = res.data;
+
+      this.setState({ fileName, innerFileStats, isTheSameVersion });
     }
     }
   }
   }
 
 
-  handleUpload({ meta, fileName, innerFileStats }) {
+  handleUpload({
+    meta, fileName, innerFileStats,
+  }) {
     this.setState({
     this.setState({
       fileName,
       fileName,
       innerFileStats,
       innerFileStats,
+      isTheSameVersion: true,
     });
     });
   }
   }
 
 
@@ -74,18 +82,51 @@ class GrowiArchiveSection extends React.Component {
     }
     }
   }
   }
 
 
+
+  handleMismatchedVersions(err) {
+    this.setState({
+      isTheSameVersion: false,
+    });
+
+  }
+
+  renderDefferentVersionAlert() {
+    const { t } = this.props;
+    return (
+      <div className="alert alert-warning mt-3">
+        {t('admin:importer_management.growi_settings.errors.different_versions')}
+      </div>
+    );
+  }
+
   resetState() {
   resetState() {
     this.setState(this.initialState);
     this.setState(this.initialState);
   }
   }
 
 
   render() {
   render() {
     const { t } = this.props;
     const { t } = this.props;
+    const { isTheSameVersion } = this.state;
 
 
     return (
     return (
       <Fragment>
       <Fragment>
         <h2>{t('admin:importer_management.import_growi_archive')}</h2>
         <h2>{t('admin:importer_management.import_growi_archive')}</h2>
-
-        {this.state.fileName != null ? (
+        <div className="card well mb-4 small">
+          <ul>
+            <li>{t('admin:importer_management.skip_username_and_email_when_overlapped')}</li>
+            <li>{t('admin:importer_management.prepare_new_account_for_migration')}</li>
+            <li>
+              <a
+                href={`${t('admin:importer_management.admin_archive_data_import_guide_url')}`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >{t('admin:importer_management.archive_data_import_detail')}
+              </a>
+            </li>
+          </ul>
+        </div>
+
+        {isTheSameVersion === false && this.renderDefferentVersionAlert()}
+        {this.state.fileName != null && isTheSameVersion === true ? (
           <div className="px-4">
           <div className="px-4">
             <ImportForm
             <ImportForm
               fileName={this.state.fileName}
               fileName={this.state.fileName}
@@ -94,10 +135,11 @@ class GrowiArchiveSection extends React.Component {
             />
             />
           </div>
           </div>
         )
         )
-          : (
-            <UploadForm
-              onUpload={this.handleUpload}
-            />
+        : (
+          <UploadForm
+            onUpload={this.handleUpload}
+            onVersionMismatch={this.handleMismatchedVersions}
+          />
           )}
           )}
       </Fragment>
       </Fragment>
     );
     );

+ 13 - 6
src/server/routes/apiv3/import.js

@@ -8,6 +8,7 @@ const multer = require('multer');
 const express = require('express');
 const express = require('express');
 
 
 const GrowiArchiveImportOption = require('@commons/models/admin/growi-archive-import-option');
 const GrowiArchiveImportOption = require('@commons/models/admin/growi-archive-import-option');
+const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
 
 
 const router = express.Router();
 const router = express.Router();
@@ -305,20 +306,26 @@ module.exports = (crowi) => {
   router.post('/upload', uploads.single('file'), accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
   router.post('/upload', uploads.single('file'), accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
     const { file } = req;
     const { file } = req;
     const zipFile = importService.getFile(file.filename);
     const zipFile = importService.getFile(file.filename);
+    let data = null;
 
 
     try {
     try {
-      const data = await growiBridgeService.parseZipFile(zipFile);
-
-      // validate with meta.json
-      importService.validate(data.meta);
-
-      return res.apiv3(data);
+      data = await growiBridgeService.parseZipFile(zipFile);
     }
     }
     catch (err) {
     catch (err) {
       // TODO: use ApiV3Error
       // TODO: use ApiV3Error
       logger.error(err);
       logger.error(err);
       return res.status(500).send({ status: 'ERROR' });
       return res.status(500).send({ status: 'ERROR' });
     }
     }
+    try {
+      // validate with meta.json
+      importService.validate(data.meta);
+      return res.apiv3(data);
+    }
+    catch {
+      const msg = 'the version of this growi and the growi that exported the data are not met';
+      const varidationErr = 'versions-are-not-met';
+      return res.apiv3Err(new ErrorV3(msg, varidationErr), 500);
+    }
   });
   });
 
 
   /**
   /**

+ 17 - 1
src/server/service/import.js

@@ -147,8 +147,24 @@ class ImportService {
 
 
     const isImporting = this.currentProgressingStatus != null;
     const isImporting = this.currentProgressingStatus != null;
 
 
+    const zipFileStat = filtered.pop();
+    let isTheSameVersion = false;
+
+    if (zipFileStat != null) {
+      try {
+        this.validate(zipFileStat.meta);
+        isTheSameVersion = true;
+      }
+      catch (err) {
+        isTheSameVersion = false;
+        logger.error('the versions are not met', err);
+      }
+    }
+
+
     return {
     return {
-      zipFileStat: filtered.pop(),
+      isTheSameVersion,
+      zipFileStat,
       isImporting,
       isImporting,
       progressList: isImporting ? this.currentProgressingStatus.progressList : null,
       progressList: isImporting ? this.currentProgressingStatus.progressList : null,
     };
     };