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

Merge pull request #8220 from weseek/imprv/132893-134449-show-group-sync-in-progress-status-on-reload

show sync status as 'in progress' on page reload
Ryoji Shimizu 2 лет назад
Родитель
Сommit
9d1108a49b

+ 13 - 0
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/SyncExecution.tsx

@@ -5,6 +5,7 @@ import {
 import { useTranslation } from 'react-i18next';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 
+import { apiv3Get } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import LabeledProgressBar from '~/components/Admin/Common/LabeledProgressBar';
 import { ExternalGroupProviderType } from '~/features/external-user-group/interfaces/external-user-group';
@@ -75,6 +76,18 @@ export const SyncExecution = ({
     };
   }, [socket, mutateExternalUserGroups, t, provider]);
 
+  // get sync status on load, since next socket data may take a while
+  useEffect(() => {
+    const getSyncStatus = async() => {
+      const res = await apiv3Get(`/external-user-groups/${provider}/sync-status`);
+      if (res.data.isExecutingSync) {
+        setSyncStatus(SyncStatus.syncExecuting);
+        setProgress({ total: res.data.totalCount, current: res.data.count });
+      }
+    };
+    getSyncStatus();
+  }, [provider]);
+
   const onSyncBtnClick = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     setCurrentSubmitEvent(e);

+ 11 - 1
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -33,7 +33,7 @@ module.exports = (crowi: Crowi): Router => {
   const activityEvent = crowi.event('activity');
 
   const isExecutingSync = () => {
-    return crowi.ldapUserGroupSyncService?.isExecutingSync || crowi.keycloakUserGroupSyncService?.isExecutingSync || false;
+    return crowi.ldapUserGroupSyncService?.syncStatus?.isExecutingSync || crowi.keycloakUserGroupSyncService?.syncStatus?.isExecutingSync || false;
   };
 
   const validators = {
@@ -378,6 +378,16 @@ module.exports = (crowi: Crowi): Router => {
     return res.apiv3({}, 202);
   });
 
+  router.get('/ldap/sync-status', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
+    const syncStatus = crowi.ldapUserGroupSyncService?.syncStatus;
+    return res.apiv3({ ...syncStatus });
+  });
+
+  router.get('/keycloak/sync-status', loginRequiredStrictly, adminRequired, (req: AuthorizedRequest, res: ApiV3Response) => {
+    const syncStatus = crowi.keycloakUserGroupSyncService?.syncStatus;
+    return res.apiv3({ ...syncStatus });
+  });
+
   return router;
 
 };

+ 27 - 23
apps/app/src/features/external-user-group/server/service/external-user-group-sync.ts

@@ -25,9 +25,11 @@ const logger = loggerFactory('growi:service:external-user-group-sync-service');
 const TREES_BATCH_SIZE = 10;
 const USERS_BATCH_SIZE = 30;
 
+type SyncStatus = { isExecutingSync: boolean, totalCount: number, count: number }
+
 class ExternalUserGroupSyncS2sMessage extends S2sMessage {
 
-  isExecutingSync: boolean;
+  syncStatus: SyncStatus;
 
 }
 
@@ -41,7 +43,7 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
 
   s2sMessagingService: S2sMessagingService | null;
 
-  isExecutingSync = false;
+  syncStatus: SyncStatus = { isExecutingSync: false, totalCount: 0, count: 0 };
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   constructor(groupProviderType: ExternalGroupProviderType, s2sMessagingService: S2sMessagingService | null, socketIoService) {
@@ -61,16 +63,16 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
    * @inheritdoc
    */
   async handleS2sMessage(s2sMessage: ExternalUserGroupSyncS2sMessage): Promise<void> {
-    logger.info(`Set isExecutingSync to ${s2sMessage.isExecutingSync} by pubsub notification`);
-    this.isExecutingSync = s2sMessage.isExecutingSync;
+    logger.info('Update syncStatus by pubsub notification');
+    this.syncStatus = s2sMessage.syncStatus;
   }
 
-  async switchIsExecutingSync(isExecutingSync: boolean): Promise<void> {
-    this.isExecutingSync = isExecutingSync;
+  async setSyncStatus(syncStatus: SyncStatus): Promise<void> {
+    this.syncStatus = syncStatus;
 
     if (this.s2sMessagingService != null) {
       const s2sMessage = new ExternalUserGroupSyncS2sMessage('switchExternalUserGroupExecSyncStatus', {
-        isExecutingSync,
+        syncStatus: this.syncStatus,
       });
 
       try {
@@ -89,31 +91,33 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
   */
   async syncExternalUserGroups(): Promise<void> {
     if (this.authProviderType == null) throw new Error('auth provider type is not set');
-    if (this.isExecutingSync) throw new Error('External user group sync is already being executed');
-    await this.switchIsExecutingSync(true);
+    if (this.syncStatus.isExecutingSync) throw new Error('External user group sync is already being executed');
 
     const preserveDeletedLdapGroups: boolean = configManager?.getConfig('crowi', `external-user-group:${this.groupProviderType}:preserveDeletedGroups`);
     const existingExternalUserGroupIds: string[] = [];
 
     const socket = this.socketIoService?.getAdminSocket();
 
+    const syncNode = async(node: ExternalUserGroupTreeNode, parentId?: string) => {
+      const externalUserGroup = await this.createUpdateExternalUserGroup(node, parentId);
+      existingExternalUserGroupIds.push(externalUserGroup._id);
+      await this.setSyncStatus({ isExecutingSync: true, totalCount: this.syncStatus.totalCount, count:  this.syncStatus.count + 1 });
+      socket?.emit(SocketEventName.externalUserGroup[this.groupProviderType].GroupSyncProgress, {
+        totalCount: this.syncStatus.totalCount, count: this.syncStatus.count,
+      });
+      // Do not use Promise.all, because the number of promises processed can
+      // exponentially grow when group tree is enormous
+      for await (const childNode of node.childGroupNodes) {
+        await syncNode(childNode, externalUserGroup._id);
+      }
+    };
+
     try {
       const trees = await this.generateExternalUserGroupTrees();
       const totalCount = trees.map(tree => this.getGroupCountOfTree(tree))
         .reduce((sum, current) => sum + current);
-      let count = 0;
-
-      const syncNode = async(node: ExternalUserGroupTreeNode, parentId?: string) => {
-        const externalUserGroup = await this.createUpdateExternalUserGroup(node, parentId);
-        existingExternalUserGroupIds.push(externalUserGroup._id);
-        count++;
-        socket?.emit(SocketEventName.externalUserGroup[this.groupProviderType].GroupSyncProgress, { totalCount, count });
-        // Do not use Promise.all, because the number of promises processed can
-        // exponentially grow when group tree is enormous
-        for await (const childNode of node.childGroupNodes) {
-          await syncNode(childNode, externalUserGroup._id);
-        }
-      };
+
+      await this.setSyncStatus({ isExecutingSync: true, totalCount, count: 0 });
 
       await batchProcessPromiseAll(trees, TREES_BATCH_SIZE, async(tree) => {
         return syncNode(tree);
@@ -134,7 +138,7 @@ abstract class ExternalUserGroupSyncService implements S2sMessageHandlable {
       socket?.emit(SocketEventName.externalUserGroup[this.groupProviderType].GroupSyncFailed);
     }
     finally {
-      await this.switchIsExecutingSync(false);
+      await this.setSyncStatus({ isExecutingSync: false, totalCount: 0, count: 0 });
     }
   }