Explorar el Código

do not await for group sync to finish to return response from API

Futa Arai hace 2 años
padre
commit
75882f9b9d

+ 28 - 10
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/KeycloakGroupManagement.tsx

@@ -19,7 +19,7 @@ export const KeycloakGroupManagement: FC = () => {
   const { data: socket } = useAdminSocket();
   const { data: socket } = useAdminSocket();
   const { mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
   const { mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
 
 
-  const [syncStatus, setSyncStatus] = useState<'beforeSync' | 'syncExecuting' | 'syncFinished'>('beforeSync');
+  const [syncStatus, setSyncStatus] = useState<'beforeSync' | 'syncExecuting' | 'syncCompleted' | 'syncFailed'>('beforeSync');
   const [progress, setProgress] = useState({
   const [progress, setProgress] = useState({
     total: 0,
     total: 0,
     current: 0,
     current: 0,
@@ -27,6 +27,7 @@ export const KeycloakGroupManagement: FC = () => {
 
 
   useEffect(() => {
   useEffect(() => {
     if (socket != null) {
     if (socket != null) {
+      socket.off(SocketEventName.GroupSyncProgress);
       socket.on(SocketEventName.GroupSyncProgress, (data) => {
       socket.on(SocketEventName.GroupSyncProgress, (data) => {
         setSyncStatus('syncExecuting');
         setSyncStatus('syncExecuting');
         setProgress({
         setProgress({
@@ -35,30 +36,47 @@ export const KeycloakGroupManagement: FC = () => {
         });
         });
       });
       });
 
 
-      socket.on(SocketEventName.FinishGroupSync, () => {
-        setSyncStatus('syncFinished');
+      socket.off(SocketEventName.GroupSyncCompleted);
+      socket.on(SocketEventName.GroupSyncCompleted, () => {
+        setSyncStatus('syncCompleted');
+        mutateExternalUserGroups();
+        toastSuccess(t('external_user_group.sync_succeeded'));
+      });
+
+      socket.off(SocketEventName.GroupSyncFailed);
+      socket.on(SocketEventName.GroupSyncFailed, () => {
+        setSyncStatus('syncFailed');
+        mutateExternalUserGroups();
+        toastError(t('external_user_group.sync_failed'));
       });
       });
     }
     }
-  }, [socket]);
+  }, [socket, mutateExternalUserGroups, t]);
 
 
   const onSyncBtnClick = useCallback(async(e) => {
   const onSyncBtnClick = useCallback(async(e) => {
     e.preventDefault();
     e.preventDefault();
-    setProgress({ total: 0, current: 0 });
-    setSyncStatus('syncExecuting');
     try {
     try {
       await apiv3Put('/external-user-groups/keycloak/sync');
       await apiv3Put('/external-user-groups/keycloak/sync');
-      toastSuccess(t('external_user_group.sync_succeeded'));
-      mutateExternalUserGroups();
+      setProgress({ total: 0, current: 0 });
+      setSyncStatus('syncExecuting');
     }
     }
     catch (errs) {
     catch (errs) {
       toastError(t(errs[0]?.code));
       toastError(t(errs[0]?.code));
     }
     }
-  }, [t, mutateExternalUserGroups]);
+  }, [t]);
 
 
   const renderProgressBar = () => {
   const renderProgressBar = () => {
     if (syncStatus === 'beforeSync') return null;
     if (syncStatus === 'beforeSync') return null;
 
 
-    const header = syncStatus === 'syncExecuting' ? 'Processing..' : 'Completed';
+    let header;
+    if (syncStatus === 'syncExecuting') {
+      header = 'Processing..';
+    }
+    else if (syncStatus === 'syncCompleted') {
+      header = 'Completed';
+    }
+    else {
+      header = 'Failed';
+    }
 
 
     return (
     return (
       <LabeledProgressBar
       <LabeledProgressBar

+ 28 - 10
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/LdapGroupManagement.tsx

@@ -20,7 +20,7 @@ export const LdapGroupManagement: FC = () => {
   const { data: socket } = useAdminSocket();
   const { data: socket } = useAdminSocket();
   const { mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
   const { mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
 
 
-  const [syncStatus, setSyncStatus] = useState<'beforeSync' | 'syncExecuting' | 'syncFinished'>('beforeSync');
+  const [syncStatus, setSyncStatus] = useState<'beforeSync' | 'syncExecuting' | 'syncCompleted' | 'syncFailed'>('beforeSync');
   const [progress, setProgress] = useState({
   const [progress, setProgress] = useState({
     total: 0,
     total: 0,
     current: 0,
     current: 0,
@@ -42,6 +42,7 @@ export const LdapGroupManagement: FC = () => {
 
 
   useEffect(() => {
   useEffect(() => {
     if (socket != null) {
     if (socket != null) {
+      socket.off(SocketEventName.GroupSyncProgress);
       socket.on(SocketEventName.GroupSyncProgress, (data) => {
       socket.on(SocketEventName.GroupSyncProgress, (data) => {
         setSyncStatus('syncExecuting');
         setSyncStatus('syncExecuting');
         setProgress({
         setProgress({
@@ -50,16 +51,24 @@ export const LdapGroupManagement: FC = () => {
         });
         });
       });
       });
 
 
-      socket.on(SocketEventName.FinishGroupSync, () => {
-        setSyncStatus('syncFinished');
+      socket.off(SocketEventName.GroupSyncCompleted);
+      socket.on(SocketEventName.GroupSyncCompleted, () => {
+        setSyncStatus('syncCompleted');
+        mutateExternalUserGroups();
+        toastSuccess(t('external_user_group.sync_succeeded'));
+      });
+
+      socket.off(SocketEventName.GroupSyncFailed);
+      socket.on(SocketEventName.GroupSyncFailed, () => {
+        setSyncStatus('syncFailed');
+        mutateExternalUserGroups();
+        toastError(t('external_user_group.sync_failed'));
       });
       });
     }
     }
-  }, [socket]);
+  }, [socket, mutateExternalUserGroups, t]);
 
 
   const onSyncBtnClick = useCallback(async(e) => {
   const onSyncBtnClick = useCallback(async(e) => {
     e.preventDefault();
     e.preventDefault();
-    setProgress({ total: 0, current: 0 });
-    setSyncStatus('syncExecuting');
     try {
     try {
       if (isUserBind) {
       if (isUserBind) {
         const password = e.target.password.value;
         const password = e.target.password.value;
@@ -68,18 +77,27 @@ export const LdapGroupManagement: FC = () => {
       else {
       else {
         await apiv3Put('/external-user-groups/ldap/sync');
         await apiv3Put('/external-user-groups/ldap/sync');
       }
       }
-      toastSuccess(t('external_user_group.sync_succeeded'));
-      mutateExternalUserGroups();
+      setProgress({ total: 0, current: 0 });
+      setSyncStatus('syncExecuting');
     }
     }
     catch (errs) {
     catch (errs) {
       toastError(t(errs[0]?.code));
       toastError(t(errs[0]?.code));
     }
     }
-  }, [t, isUserBind, mutateExternalUserGroups]);
+  }, [t, isUserBind]);
 
 
   const renderProgressBar = () => {
   const renderProgressBar = () => {
     if (syncStatus === 'beforeSync') return null;
     if (syncStatus === 'beforeSync') return null;
 
 
-    const header = syncStatus === 'syncExecuting' ? 'Processing..' : 'Completed';
+    let header;
+    if (syncStatus === 'syncExecuting') {
+      header = 'Processing..';
+    }
+    else if (syncStatus === 'syncCompleted') {
+      header = 'Completed';
+    }
+    else {
+      header = 'Failed';
+    }
 
 
     return (
     return (
       <LabeledProgressBar
       <LabeledProgressBar

+ 6 - 16
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -317,15 +317,10 @@ module.exports = (crowi: Crowi): Router => {
       );
       );
     }
     }
 
 
-    try {
-      await ldapUserGroupSyncService?.syncExternalUserGroups({ userBindUsername: req.user.name, userBindPassword: req.body.password });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(new ErrorV3('Sync failed', 'external_user_group.sync_failed'), 500);
-    }
+    // Do not await for sync to finish. Result (completed, failed) will be notified to the client by socket-io.
+    ldapUserGroupSyncService?.syncExternalUserGroups({ userBindUsername: req.user.name, userBindPassword: req.body.password });
 
 
-    return res.apiv3({}, 204);
+    return res.apiv3({}, 202);
   });
   });
 
 
   router.put('/keycloak/sync', loginRequiredStrictly, adminRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
   router.put('/keycloak/sync', loginRequiredStrictly, adminRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
@@ -363,15 +358,10 @@ module.exports = (crowi: Crowi): Router => {
       );
       );
     }
     }
 
 
-    try {
-      await keycloakUserGroupSyncService?.syncExternalUserGroups(authProviderType);
-    }
-    catch (err) {
-      logger.error(err);
-      return res.apiv3Err(new ErrorV3('Sync failed', 'external_user_group.sync_failed'), 500);
-    }
+    // Do not await for sync to finish. Result (completed, failed) will be notified to the client by socket-io.
+    keycloakUserGroupSyncService?.syncExternalUserGroups(authProviderType);
 
 
-    return res.apiv3({}, 204);
+    return res.apiv3({}, 202);
   });
   });
 
 
   return router;
   return router;

+ 8 - 1
apps/app/src/features/external-user-group/server/service/external-user-group-sync.ts

@@ -3,6 +3,7 @@ import type { IUserHasId } from '@growi/core';
 import { SocketEventName } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
 import ExternalAccount from '~/server/models/external-account';
 import ExternalAccount from '~/server/models/external-account';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 import { excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
+import loggerFactory from '~/utils/logger';
 import { batchProcessPromiseAll } from '~/utils/promise';
 import { batchProcessPromiseAll } from '~/utils/promise';
 
 
 import { configManager } from '../../../../server/service/config-manager';
 import { configManager } from '../../../../server/service/config-manager';
@@ -13,6 +14,8 @@ import {
 import ExternalUserGroup from '../models/external-user-group';
 import ExternalUserGroup from '../models/external-user-group';
 import ExternalUserGroupRelation from '../models/external-user-group-relation';
 import ExternalUserGroupRelation from '../models/external-user-group-relation';
 
 
+const logger = loggerFactory('growi:service:external-user-group-sync-service');
+
 // When d = max depth of group trees
 // When d = max depth of group trees
 // Max space complexity of syncExternalUserGroups will be:
 // Max space complexity of syncExternalUserGroups will be:
 // O(TREES_BATCH_SIZE * d * USERS_BATCH_SIZE)
 // O(TREES_BATCH_SIZE * d * USERS_BATCH_SIZE)
@@ -76,10 +79,14 @@ abstract class ExternalUserGroupSyncService<SyncParamsType = any> {
         await ExternalUserGroup.deleteMany({ _id: { $nin: existingExternalUserGroupIds }, groupProviderType: this.groupProviderType });
         await ExternalUserGroup.deleteMany({ _id: { $nin: existingExternalUserGroupIds }, groupProviderType: this.groupProviderType });
         await ExternalUserGroupRelation.removeAllInvalidRelations();
         await ExternalUserGroupRelation.removeAllInvalidRelations();
       }
       }
+      socket?.emit(SocketEventName.GroupSyncCompleted);
+    }
+    catch (e) {
+      logger.error(e.message);
+      socket?.emit(SocketEventName.GroupSyncFailed);
     }
     }
     finally {
     finally {
       this.isExecutingSync = false;
       this.isExecutingSync = false;
-      socket?.emit(SocketEventName.FinishGroupSync);
     }
     }
   }
   }
 
 

+ 1 - 1
apps/app/src/features/external-user-group/server/service/ldap-user-group-sync.ts

@@ -10,7 +10,7 @@ import {
 
 
 import ExternalUserGroupSyncService from './external-user-group-sync';
 import ExternalUserGroupSyncService from './external-user-group-sync';
 
 
-const logger = loggerFactory('growi:service:ldap-user-sync-service');
+const logger = loggerFactory('growi:service:ldap-user-group-sync-service');
 
 
 // When d = max depth of group trees
 // When d = max depth of group trees
 // Max space complexity of generateExternalUserGroupTrees will be:
 // Max space complexity of generateExternalUserGroupTrees will be:

+ 2 - 1
apps/app/src/interfaces/websocket.ts

@@ -19,7 +19,8 @@ export const SocketEventName = {
 
 
   // External user group sync
   // External user group sync
   GroupSyncProgress: 'groupSyncProgress',
   GroupSyncProgress: 'groupSyncProgress',
-  FinishGroupSync: 'finishGroupSync',
+  GroupSyncFailed: 'groupSyncFailed',
+  GroupSyncCompleted: 'groupSyncCompleted',
 
 
   // Page Operation
   // Page Operation
   PageCreated: 'page:create',
   PageCreated: 'page:create',