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

Implemented counting process & Improved methods

Taichi Masuyama 4 лет назад
Родитель
Сommit
7e67281cc1
2 измененных файлов с 81 добавлено и 23 удалено
  1. 2 1
      packages/app/src/server/models/page.ts
  2. 79 22
      packages/app/src/server/service/page.ts

+ 2 - 1
packages/app/src/server/models/page.ts

@@ -458,6 +458,7 @@ schema.statics.recountDescendantCountOfSelfAndDescendants = async function(id: O
         $project: {
           path: 1,
           parent: 1,
+          isEmpty: 1,
           descendantCount: 1,
         },
       },
@@ -468,7 +469,7 @@ schema.statics.recountDescendantCountOfSelfAndDescendants = async function(id: O
             $sum: '$descendantCount',
           },
           sumOfDocsCount: {
-            $sum: 1,
+            $sum: { $cond: { if: { $ne: ['$isEmpty', true] }, then: 0, else: 1 } }, // exclude isEmpty true page from sumOfDocsCount
           },
         },
       },

+ 79 - 22
packages/app/src/server/service/page.ts

@@ -892,7 +892,7 @@ class PageService {
 
     const duplicateDescendants = this.duplicateDescendants.bind(this);
     const shouldNormalizeParent = this.shouldNormalizeParent.bind(this);
-    const normalizeParentRecursively = this.normalizeParentRecursively.bind(this);
+    const normalizeParentAndDescendantCountOfDescendants = this.normalizeParentAndDescendantCountOfDescendants.bind(this);
     const pageEvent = this.pageEvent;
     let count = 0;
     const writeStream = new Writable({
@@ -914,9 +914,7 @@ class PageService {
         const shouldNormalize = shouldNormalizeParent(page);
         if (shouldNormalize) {
           try {
-            const escapedPath = escapeStringRegexp(newPagePath);
-            const regexps = [new RegExp(`^${escapedPath}`, 'i')];
-            await normalizeParentRecursively(null, regexps);
+            await normalizeParentAndDescendantCountOfDescendants(newPagePath);
             logger.info(`Successfully normalized duplicated descendant pages under "${newPagePath}"`);
           }
           catch (err) {
@@ -1010,16 +1008,15 @@ class PageService {
     }
 
     if (isRecursively) {
-      const deleteDescendantsWithStream = this.deleteDescendantsWithStream.bind(this);
-
       // no await for deleteDescendantsWithStream and updateDescendantCountOfAncestors
       (async() => {
-        const deletedCount = await deleteDescendantsWithStream(page, user, shouldUseV4Process); // use the same process in both version v4 and v5
+        const deletedCount = await this.deleteDescendantsWithStream(page, user, shouldUseV4Process); // use the same process in both version v4 and v5
 
         // update descendantCount of ancestors'
-        const exParent = await Page.findOne({ _id: page.parent });
-        if (exParent != null) {
-          await this.updateDescendantCountOfAncestors(exParent._id, deletedCount * -1, true);
+        if (page.parent != null) {
+          await this.updateDescendantCountOfAncestors(page.parent, deletedCount * -1, true);
+
+          // TODO https://redmine.weseek.co.jp/issues/87667 : delete leaf empty pages here
         }
       })();
     }
@@ -1029,6 +1026,11 @@ class PageService {
       if (shouldReplace) {
         await Page.replaceTargetWithEmptyPage(page);
       }
+
+      const shouldDeleteLeafEmptyPages = !shouldReplace;
+      if (shouldDeleteLeafEmptyPages) {
+        // TODO https://redmine.weseek.co.jp/issues/87667 : delete leaf empty pages here
+      }
     }
 
     let deletedPage;
@@ -1175,9 +1177,13 @@ class PageService {
 
     const deleteDescendants = this.deleteDescendants.bind(this);
     let count = 0;
+    let nDeletedNonEmptyPages = 0; // used for updating descendantCount
+
     const writeStream = new Writable({
       objectMode: true,
       async write(batch, encoding, callback) {
+        nDeletedNonEmptyPages += batch.filter(d => !d.isEmpty).length;
+
         try {
           count += batch.length;
           await deleteDescendants(batch, user);
@@ -1202,7 +1208,7 @@ class PageService {
 
     await streamToPromise(readStream);
 
-    return count;
+    return nDeletedNonEmptyPages;
   }
 
   private async deleteCompletelyOperation(pageIds, pagePaths) {
@@ -1272,7 +1278,22 @@ class PageService {
     await this.deleteCompletelyOperation(ids, paths);
 
     if (isRecursively) {
-      this.deleteCompletelyDescendantsWithStream(page, user, options, shouldUseV4Process);
+      // no await for deleteCompletelyDescendantsWithStream
+      (async() => {
+        const deletedCount = await this.deleteCompletelyDescendantsWithStream(page, user, options, shouldUseV4Process);
+
+        // update descendantCount of ancestors'
+        if (page.parent != null) {
+          await this.updateDescendantCountOfAncestors(page.parent, deletedCount * -1, true);
+        }
+
+        // TODO https://redmine.weseek.co.jp/issues/87667 : delete leaf empty pages here
+      })();
+    }
+    else {
+      await this.updateDescendantCountOfAncestors(page.parent, -1, true);
+
+      // TODO https://redmine.weseek.co.jp/issues/87667 : delete leaf empty pages here
     }
 
     if (!page.isEmpty && !preventEmitting) {
@@ -1308,7 +1329,7 @@ class PageService {
   /**
    * Create delete completely stream
    */
-  private async deleteCompletelyDescendantsWithStream(targetPage, user, options = {}, shouldUseV4Process = true) {
+  private async deleteCompletelyDescendantsWithStream(targetPage, user, options = {}, shouldUseV4Process = true): Promise<number> {
     let readStream;
 
     if (shouldUseV4Process) { // pages don't have parents
@@ -1319,11 +1340,15 @@ class PageService {
       readStream = await factory.generateReadable();
     }
 
-    const deleteMultipleCompletely = this.deleteMultipleCompletely.bind(this);
     let count = 0;
+    let nDeletedNonEmptyPages = 0; // used for updating descendantCount
+
+    const deleteMultipleCompletely = this.deleteMultipleCompletely.bind(this);
     const writeStream = new Writable({
       objectMode: true,
       async write(batch, encoding, callback) {
+        nDeletedNonEmptyPages += batch.filter(d => !d.isEmpty).length;
+
         try {
           count += batch.length;
           await deleteMultipleCompletely(batch, user, options);
@@ -1345,6 +1370,10 @@ class PageService {
     readStream
       .pipe(createBatchStream(BULK_REINDEX_SIZE))
       .pipe(writeStream);
+
+    await streamToPromise(readStream);
+
+    return nDeletedNonEmptyPages;
   }
 
   // use the same process in both v4 and v5
@@ -1408,13 +1437,26 @@ class PageService {
     page.lastUpdateUser = user;
     const updatedPage = await Page.findByIdAndUpdate(page._id, {
       $set: {
-        path: newPath, status: Page.STATUS_PUBLISHED, lastUpdateUser: user._id, deleteUser: null, deletedAt: null, parent: parent._id,
+        path: newPath, status: Page.STATUS_PUBLISHED, lastUpdateUser: user._id, deleteUser: null, deletedAt: null, parent: parent._id, descendantCount: 0,
       },
     }, { new: true });
     await PageTagRelation.updateMany({ relatedPage: page._id }, { $set: { isPageTrashed: false } });
 
     if (isRecursively) {
-      this.revertDeletedDescendantsWithStream(page, user, options, shouldUseV4Process);
+      // no await for revertDeletedDescendantsWithStream
+      (async() => {
+        const revertedCount = await this.revertDeletedDescendantsWithStream(page, user, options, shouldUseV4Process);
+
+        // update descendantCount of ancestors'
+        if (page.parent != null) {
+          await this.updateDescendantCountOfAncestors(page.parent, revertedCount * -1, true);
+
+          // TODO https://redmine.weseek.co.jp/issues/87667 : delete leaf empty pages here
+        }
+      })();
+    }
+    else {
+      await this.updateDescendantCountOfAncestors(parent._id, 1, true);
     }
 
     return updatedPage;
@@ -1450,7 +1492,7 @@ class PageService {
   /**
    * Create revert stream
    */
-  private async revertDeletedDescendantsWithStream(targetPage, user, options = {}, shouldUseV4Process = true) {
+  private async revertDeletedDescendantsWithStream(targetPage, user, options = {}, shouldUseV4Process = true): Promise<number> {
     if (shouldUseV4Process) {
       return this.revertDeletedDescendantsWithStreamV4(targetPage, user, options);
     }
@@ -1458,7 +1500,7 @@ class PageService {
     const readStream = await this.generateReadStreamToOperateOnlyDescendants(targetPage.path, user);
 
     const revertDeletedDescendants = this.revertDeletedDescendants.bind(this);
-    const normalizeParentRecursively = this.normalizeParentRecursively.bind(this);
+    const normalizeParentAndDescendantCountOfDescendants = this.normalizeParentAndDescendantCountOfDescendants.bind(this);
     const shouldNormalizeParent = this.shouldNormalizeParent.bind(this);
     let count = 0;
     const writeStream = new Writable({
@@ -1482,9 +1524,7 @@ class PageService {
         if (shouldNormalize) {
           try {
             const newPath = Page.getRevertDeletedPageName(targetPage.path);
-            const escapedPath = escapeStringRegexp(newPath);
-            const regexps = [new RegExp(`^${escapedPath}`, 'i')];
-            await normalizeParentRecursively(null, regexps);
+            await normalizeParentAndDescendantCountOfDescendants(newPath);
             logger.info(`Successfully normalized reverted descendant pages under "${newPath}"`);
           }
           catch (err) {
@@ -1501,6 +1541,10 @@ class PageService {
     readStream
       .pipe(createBatchStream(BULK_REINDEX_SIZE))
       .pipe(writeStream);
+
+    await streamToPromise(readStream);
+
+    return count;
   }
 
   private async revertDeletedDescendantsWithStreamV4(targetPage, user, options = {}) {
@@ -1532,6 +1576,10 @@ class PageService {
     readStream
       .pipe(createBatchStream(BULK_REINDEX_SIZE))
       .pipe(writeStream);
+
+    await streamToPromise(readStream);
+
+    return count;
   }
 
 
@@ -1837,6 +1885,15 @@ class PageService {
     }
   }
 
+  private async normalizeParentAndDescendantCountOfDescendants(path: string): Promise<void> {
+    const escapedPath = escapeStringRegexp(path);
+    const regexps = [new RegExp(`^${escapedPath}`, 'i')];
+    await this.normalizeParentRecursively(null, regexps);
+
+    // update descendantCount of descendant pages
+    await this.updateDescendantCountOfSelfAndDescendants(path);
+  }
+
   // TODO: use websocket to show progress
   private async normalizeParentRecursively(grant, regexps, publicOnly = false): Promise<void> {
     const BATCH_SIZE = 100;
@@ -2025,7 +2082,7 @@ class PageService {
    * - page that has the same path as the provided path
    * - pages that are descendants of the above page
    */
-  async updateDescendantCountOfSelfAndDescendants(path = '/') {
+  async updateDescendantCountOfSelfAndDescendants(path) {
     const BATCH_SIZE = 200;
     const Page = this.crowi.model('Page');