Taichi Masuyama 4 سال پیش
والد
کامیت
cd2967cb11

+ 3 - 8
packages/app/src/client/services/AdminAppContainer.js

@@ -453,17 +453,12 @@ export default class AdminAppContainer extends Container {
   /**
   /**
    * Start v5 page migration
    * Start v5 page migration
    * @memberOf AdminAppContainer
    * @memberOf AdminAppContainer
-   * @property action takes either 'notNow' or 'upgrade'. 'upgrade' will start or resume migration
+   * @property action takes only 'upgrade' for now. 'upgrade' will start or resume migration
    */
    */
   async v5PageMigrationHandler(action) {
   async v5PageMigrationHandler(action) {
-    // not wait response and immediately set state to give priority to response time
-    if (action === 'notNow') {
-      this.changeIsV5Compatible(false);
-    }
-
     const response = await this.appContainer.apiv3.post('/pages/v5-schema-migration', { action });
     const response = await this.appContainer.apiv3.post('/pages/v5-schema-migration', { action });
-    const { status } = response.data;
-    return { status };
+    const { isV5Compatible } = response.data;
+    return { isV5Compatible };
   }
   }
 
 
 }
 }

+ 0 - 10
packages/app/src/components/Admin/App/V5PageMigration.tsx

@@ -10,7 +10,6 @@ import { toastSuccess, toastError } from '../../../client/util/apiNotification';
 const V5PageMigration: FC<any> = (props) => {
 const V5PageMigration: FC<any> = (props) => {
   const [isV5PageMigrationModalShown, setIsV5PageMigrationModalShown] = useState(false);
   const [isV5PageMigrationModalShown, setIsV5PageMigrationModalShown] = useState(false);
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
-  const { isV5Compatible } = adminAppContainer.state;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const onConfirm = async() => {
   const onConfirm = async() => {
@@ -24,11 +23,6 @@ const V5PageMigration: FC<any> = (props) => {
     }
     }
   };
   };
 
 
-  const onNotNowClicked = async() => {
-    // not show toastr
-    await adminAppContainer.v5PageMigrationHandler('notNow');
-  };
-
   return (
   return (
     <>
     <>
       <V5PageMigrationModal
       <V5PageMigrationModal
@@ -45,10 +39,6 @@ const V5PageMigration: FC<any> = (props) => {
       </p>
       </p>
       <div className="row my-3">
       <div className="row my-3">
         <div className="mx-auto">
         <div className="mx-auto">
-          {
-            isV5Compatible == null
-            && (<button type="button" className="btn btn-secondary mr-3" onClick={() => onNotNowClicked()}>Not now</button>)
-          }
           <button type="button" className="btn btn-warning" onClick={() => setIsV5PageMigrationModalShown(true)}>Upgrade to v5</button>
           <button type="button" className="btn btn-warning" onClick={() => setIsV5PageMigrationModalShown(true)}>Upgrade to v5</button>
         </div>
         </div>
       </div>
       </div>

+ 13 - 12
packages/app/src/components/Admin/App/V5PageMigrationModal.tsx

@@ -1,5 +1,4 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
-import PropTypes from 'prop-types';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
@@ -7,15 +6,23 @@ import { useTranslation } from 'react-i18next';
 
 
 type V5PageMigrationModalProps = {
 type V5PageMigrationModalProps = {
   isModalOpen: boolean
   isModalOpen: boolean
-  onConfirm: () => Promise<void>;
-  onCancel: () => void;
+  onConfirm?: () => Promise<void>;
+  onCancel?: () => void;
 };
 };
 
 
-export const V5PageMigrationModal: FC<V5PageMigrationModalProps> = (props) => {
+export const V5PageMigrationModal: FC<V5PageMigrationModalProps> = (props: V5PageMigrationModalProps) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const onCancel = () => {
   const onCancel = () => {
-    props.onCancel();
+    if (props.onCancel != null) {
+      props.onCancel();
+    }
+  };
+
+  const onConfirm = () => {
+    if (props.onConfirm != null) {
+      props.onConfirm();
+    }
   };
   };
 
 
   return (
   return (
@@ -38,7 +45,7 @@ export const V5PageMigrationModal: FC<V5PageMigrationModalProps> = (props) => {
         <button
         <button
           type="button"
           type="button"
           className="btn btn-outline-primary ml-3"
           className="btn btn-outline-primary ml-3"
-          onClick={props.onConfirm}
+          onClick={onConfirm}
         >
         >
           {t('v5_page_migration.start_upgrading')}
           {t('v5_page_migration.start_upgrading')}
         </button>
         </button>
@@ -46,9 +53,3 @@ export const V5PageMigrationModal: FC<V5PageMigrationModalProps> = (props) => {
     </Modal>
     </Modal>
   );
   );
 };
 };
-
-V5PageMigrationModal.propTypes = {
-  isModalOpen: PropTypes.bool.isRequired,
-  onConfirm: PropTypes.func.isRequired,
-  onCancel: PropTypes.func.isRequired,
-};

+ 1 - 1
packages/app/src/server/models/page.js

@@ -38,7 +38,7 @@ const STATUS_DELETED = 'deleted';
 
 
 const pageSchema = new mongoose.Schema({
 const pageSchema = new mongoose.Schema({
   parent: {
   parent: {
-    type: ObjectId, ref: 'Page', default: null,
+    type: ObjectId, ref: 'Page', index: true, default: null,
   },
   },
   isEmpty: { type: Boolean, default: false },
   isEmpty: { type: Boolean, default: false },
   path: {
   path: {

+ 4 - 15
packages/app/src/server/routes/apiv3/pages.js

@@ -184,6 +184,9 @@ module.exports = (crowi) => {
       body('pageNameInput').trim().isLength({ min: 1 }).withMessage('pageNameInput is required'),
       body('pageNameInput').trim().isLength({ min: 1 }).withMessage('pageNameInput is required'),
       body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'),
       body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'),
     ],
     ],
+    v5PageMigration: [
+      body('action').isString().withMessage('action is required'),
+    ],
   };
   };
 
 
   async function createPageAction({
   async function createPageAction({
@@ -681,25 +684,11 @@ module.exports = (crowi) => {
 
 
   });
   });
 
 
-  // TODO: add validator for the action property
   // TODO: use socket conn to show progress
   // TODO: use socket conn to show progress
-  router.post('/v5-schema-migration', /* accessTokenParser, loginRequired, adminRequired, csrf, */ async(req, res) => {
+  router.post('/v5-schema-migration', accessTokenParser, loginRequired, adminRequired, csrf, validator.v5PageMigration, apiV3FormValidator, async(req, res) => {
     const { action } = req.body;
     const { action } = req.body;
 
 
     switch (action) {
     switch (action) {
-      case 'notNow':
-        try {
-          // set notNow
-          await crowi.configManager.updateConfigsInTheSameNamespace('crowi', {
-            'app:isV5Compatible': false,
-          });
-        }
-        catch (err) {
-          // not throw since this is not important
-          logger.error('Error occurred while updating app:isV5Compatible.', err);
-        }
-        break;
-
       case 'upgrade':
       case 'upgrade':
         try {
         try {
           const Page = crowi.model('Page');
           const Page = crowi.model('Page');

+ 17 - 12
packages/app/src/server/service/page.js

@@ -740,17 +740,14 @@ class PageService {
 
 
   async v5RecursiveMigration(grant, rootPath = null) {
   async v5RecursiveMigration(grant, rootPath = null) {
     const BATCH_SIZE = 100;
     const BATCH_SIZE = 100;
+    const PAGES_LIMIT = 1000;
     const Page = this.crowi.model('Page');
     const Page = this.crowi.model('Page');
     const { PageQueryBuilder } = Page;
     const { PageQueryBuilder } = Page;
 
 
-    const randomPagesStream = await Page
+    const total = await Page.countDocuments({ grant, parent: null });
+
+    let baseAggregation = Page
       .aggregate([
       .aggregate([
-        // TODO: randomize somehow sample does not work when the result is under 100?
-        // {
-        //   $sample: {
-        //     size: BATCH_SIZE,
-        //   },
-        // },
         {
         {
           $match: {
           $match: {
             grant,
             grant,
@@ -763,13 +760,20 @@ class PageService {
             path: 1,
             path: 1,
           },
           },
         },
         },
-      ])
-      .cursor({ batchSize: BATCH_SIZE }) // get stream
-      .exec();
+      ]);
+
+    // limit pages to get
+    if (total > PAGES_LIMIT) {
+      baseAggregation = baseAggregation.limit(Math.floor(total * 0.3));
+    }
+
+    const randomPagesStream = await baseAggregation.cursor({ batchSize: BATCH_SIZE }).exec();
 
 
     // use batch stream
     // use batch stream
     const batchStream = createBatchStream(BATCH_SIZE);
     const batchStream = createBatchStream(BATCH_SIZE);
 
 
+    let countPages = 0;
+
     // migrate all siblings for each page
     // migrate all siblings for each page
     const migratePagesStream = new Writable({
     const migratePagesStream = new Writable({
       objectMode: true,
       objectMode: true,
@@ -813,7 +817,6 @@ class PageService {
           // modify to adjust for RegExp
           // modify to adjust for RegExp
           const parentPath = parent.path === '/' ? '' : parent.path;
           const parentPath = parent.path === '/' ? '' : parent.path;
 
 
-          // TODO: consider filter to improve the target selection
           return {
           return {
             updateMany: {
             updateMany: {
               filter: {
               filter: {
@@ -828,7 +831,9 @@ class PageService {
           };
           };
         });
         });
         try {
         try {
-          await Page.bulkWrite(updateManyOperations);
+          const res = await Page.bulkWrite(updateManyOperations);
+          countPages += (res.items || []).length;
+          logger.info(`Page migration processing: (count=${countPages}, errors=${res.errors}, took=${res.took}ms)`);
         }
         }
         catch (err) {
         catch (err) {
           logger.error('Failed to update page.parent.', err);
           logger.error('Failed to update page.parent.', err);