Răsfoiți Sursa

Merge pull request #5768 from weseek/feat/92133-page-migration-toast-backend

feat: 92133 page migration toast backend
yuken 4 ani în urmă
părinte
comite
eb7b151d47

+ 30 - 13
packages/app/src/components/PrivateLegacyPages.tsx

@@ -1,32 +1,33 @@
 import React, {
-  useCallback, useMemo, useRef, useState,
+  useCallback, useMemo, useRef, useState, useEffect,
 } from 'react';
-import { useTranslation } from 'react-i18next';
 
+import { useTranslation } from 'react-i18next';
 import {
   UncontrolledButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem,
 } from 'reactstrap';
 
-import { IFormattedSearchResult } from '~/interfaces/search';
-import AppContainer from '~/client/services/AppContainer';
 import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
+import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess } from '~/client/util/apiNotification';
-import {
-  useSWRxSearch,
-} from '~/stores/search';
+import { V5MigrationStatus } from '~/interfaces/page-listing-results';
+import { IFormattedSearchResult } from '~/interfaces/search';
+import { PageMigrationErrorData, SocketEventName } from '~/interfaces/websocket';
 import {
   ILegacyPrivatePage, useLegacyPrivatePagesMigrationModal,
 } from '~/stores/modal';
+import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
+import {
+  useSWRxSearch,
+} from '~/stores/search';
+import { useGlobalSocket } from '~/stores/websocket';
 
-import PaginationWrapper from './PaginationWrapper';
-import { OperateAllControl } from './SearchPage/OperateAllControl';
-
-import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage2/SearchPageBase';
 import { MenuItemType } from './Common/Dropdown/PageItemControl';
 import { LegacyPrivatePagesMigrationModal } from './LegacyPrivatePagesMigrationModal';
+import PaginationWrapper from './PaginationWrapper';
+import { OperateAllControl } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
-import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
-import { V5MigrationStatus } from '~/interfaces/page-listing-results';
+import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage2/SearchPageBase';
 
 
 // TODO: replace with "customize:showPageLimitationS"
@@ -166,6 +167,22 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
   }, []);
 
   const { open: openModal, close: closeModal } = useLegacyPrivatePagesMigrationModal();
+  const { data: socket } = useGlobalSocket();
+
+  useEffect(() => {
+    socket?.on(SocketEventName.PageMigrationSuccess, () => {
+      // page migration success
+    });
+
+    socket?.on(SocketEventName.PageMigrationError, (data: PageMigrationErrorData) => {
+      // page migration error
+    });
+
+    return () => {
+      socket?.off(SocketEventName.PageMigrationSuccess);
+      socket?.off(SocketEventName.PageMigrationError);
+    };
+  }, [socket]);
 
   const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => {
     const instance = searchPageBaseRef.current;

+ 6 - 0
packages/app/src/interfaces/websocket.ts

@@ -7,6 +7,10 @@ export const SocketEventName = {
   PMMigrating: 'PublicMigrationMigrating',
   PMErrorCount: 'PublicMigrationErrorCount',
   PMEnded: 'PublicMigrationEnded',
+
+  // Page migration
+  PageMigrationSuccess: 'PageMigrationSuccess',
+  PageMigrationError: 'PageMigrationError',
 } as const;
 export type SocketEventName = typeof SocketEventName[keyof typeof SocketEventName];
 
@@ -22,3 +26,5 @@ export type PMStartedData = { total: number };
 export type PMMigratingData = { count: number };
 export type PMErrorCountData = { skip: number };
 export type PMEndedData = { isSucceeded: boolean };
+
+export type PageMigrationErrorData = { paths: string[] }

+ 6 - 1
packages/app/src/server/routes/apiv3/pages.js

@@ -791,7 +791,12 @@ module.exports = (crowi) => {
     }
 
     try {
-      await crowi.pageService.normalizeParentByPageIds(pageIds, req.user, isRecursively);
+      if (isRecursively) {
+        await crowi.pageService.normalizeParentByPageIdsRecursively(pageIds, req.user);
+      }
+      else {
+        await crowi.pageService.normalizeParentByPageIds(pageIds, req.user);
+      }
     }
     catch (err) {
       return res.apiv3Err(new ErrorV3(`Failed to migrate pages: ${err.message}`), 500);

+ 40 - 19
packages/app/src/server/service/page.ts

@@ -16,7 +16,7 @@ import {
   PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation,
 } from '~/interfaces/page-delete-config';
 import { IUserHasId } from '~/interfaces/user';
-import { SocketEventName, UpdateDescCountRawData } from '~/interfaces/websocket';
+import { PageMigrationErrorData, SocketEventName, UpdateDescCountRawData } from '~/interfaces/websocket';
 import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
 import {
   CreateMethod, PageCreateOptions, PageModel, PageDocument,
@@ -2251,24 +2251,37 @@ class PageService {
     await inAppNotificationService.emitSocketIo(targetUsers);
   }
 
-  async normalizeParentByPageIds(pageIds: ObjectIdLike[], user, isRecursively: boolean): Promise<void> {
+  async normalizeParentByPageIdsRecursively(pageIds: ObjectIdLike[], user): Promise<void> {
     const Page = mongoose.model('Page') as unknown as PageModel;
 
-    if (isRecursively) {
-      const pages = await Page.findByIdsAndViewer(pageIds, user, null);
+    const pages = await Page.findByIdsAndViewer(pageIds, user, null);
 
-      // DO NOT await !!
-      this.normalizeParentRecursivelyByPages(pages, user);
+    if (pages == null || pages.length === 0) {
+      throw Error('pageIds is null or 0 length.');
+    }
 
-      return;
+    if (pages.length > LIMIT_FOR_MULTIPLE_PAGE_OP) {
+      throw Error(`The maximum number of pageIds allowed is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`);
     }
 
+    this.normalizeParentRecursivelyByPages(pages, user);
+
+    return;
+  }
+
+  async normalizeParentByPageIds(pageIds: ObjectIdLike[], user): Promise<void> {
+    const Page = await mongoose.model('Page') as unknown as PageModel;
+
+    const socket = this.crowi.socketIoService.getDefaultSocket();
+
     for await (const pageId of pageIds) {
       const page = await Page.findById(pageId);
       if (page == null) {
         continue;
       }
 
+      const errorData: PageMigrationErrorData = { paths: [page.path] };
+
       try {
         const canOperate = await this.crowi.pageOperationService.canOperate(false, page.path, page.path);
         if (!canOperate) {
@@ -2278,14 +2291,16 @@ class PageService {
         const normalizedPage = await this.normalizeParentByPage(page, user);
 
         if (normalizedPage == null) {
+          socket.emit(SocketEventName.PageMigrationError, errorData);
           logger.error(`Failed to update descendantCount of page of id: "${pageId}"`);
         }
       }
       catch (err) {
+        socket.emit(SocketEventName.PageMigrationError, errorData);
         logger.error('Something went wrong while normalizing parent.', err);
-        // socket.emit('normalizeParentByPageIds', { error: err.message }); TODO: use socket to tell user
       }
     }
+    socket.emit(SocketEventName.PageMigrationSuccess);
   }
 
   private async normalizeParentByPage(page, user) {
@@ -2348,14 +2363,7 @@ class PageService {
     /*
      * Main Operation
      */
-    if (pages == null || pages.length === 0) {
-      logger.error('pageIds is null or 0 length.');
-      return;
-    }
-
-    if (pages.length > LIMIT_FOR_MULTIPLE_PAGE_OP) {
-      throw Error(`The maximum number of pageIds allowed is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`);
-    }
+    const socket = this.crowi.socketIoService.getDefaultSocket();
 
     const pagesToNormalize = omitDuplicateAreaPageFromPages(pages);
 
@@ -2365,25 +2373,29 @@ class PageService {
       [normalizablePages, nonNormalizablePages] = await this.crowi.pageGrantService.separateNormalizableAndNotNormalizablePages(user, pagesToNormalize);
     }
     catch (err) {
+      socket.emit(SocketEventName.PageMigrationError);
       throw err;
     }
 
     if (normalizablePages.length === 0) {
-      // socket.emit('normalizeParentRecursivelyByPages', { error: err.message }); TODO: use socket to tell user
+      socket.emit(SocketEventName.PageMigrationError);
       return;
     }
 
     if (nonNormalizablePages.length !== 0) {
-      // TODO: iterate nonNormalizablePages and send socket error to client so that the user can know which path failed to migrate
-      // socket.emit('normalizeParentRecursivelyByPages', { error: err.message }); TODO: use socket to tell user
+      const nonNormalizablePagePaths: string[] = nonNormalizablePages.map(p => p.path);
+      socket.emit(SocketEventName.PageMigrationError, { paths: nonNormalizablePagePaths });
+      logger.debug('Some pages could not be converted.', nonNormalizablePagePaths);
     }
 
     /*
      * Main Operation (s)
      */
+    const errorPagePaths: string[] = [];
     for await (const page of normalizablePages) {
       const canOperate = await this.crowi.pageOperationService.canOperate(true, page.path, page.path);
       if (!canOperate) {
+        errorPagePaths.push(page.path);
         throw Error(`Cannot operate normalizeParentRecursiively to path "${page.path}" right now.`);
       }
 
@@ -2395,6 +2407,7 @@ class PageService {
       const existingPage = await builder.query.exec();
 
       if (existingPage?.parent != null) {
+        errorPagePaths.push(page.path);
         throw Error('This page has already converted.');
       }
 
@@ -2410,6 +2423,7 @@ class PageService {
         });
       }
       catch (err) {
+        errorPagePaths.push(page.path);
         logger.error('Failed to create PageOperation document.', err);
         throw err;
       }
@@ -2418,10 +2432,17 @@ class PageService {
         await this.normalizeParentRecursivelyMainOperation(page, user, pageOp._id);
       }
       catch (err) {
+        errorPagePaths.push(page.path);
         logger.err('Failed to run normalizeParentRecursivelyMainOperation.', err);
         throw err;
       }
     }
+    if (errorPagePaths.length === 0) {
+      socket.emit(SocketEventName.PageMigrationSuccess);
+    }
+    else {
+      socket.emit(SocketEventName.PageMigrationError, { paths: errorPagePaths });
+    }
   }
 
   async normalizeParentRecursivelyMainOperation(page, user, pageOpId: ObjectIdLike): Promise<void> {