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

imprv: User group listing api (#5058)

* CC to FC

* Improved userGroupTable

* SWRized

* Updated table & home page component

* Removed hard code

* Implemented listing api

* Fixed lint error

* Implemented key serializer for swr

* Omit serialize middleware

* Upgraded swr ^1.0.1 => ^1.1.2

* Improved sync

* Fixed lint errors

* Improved nukey

* Improved types

* Improved interface

* Improved type validation

* No population at model layer
Haku Mizuki 4 лет назад
Родитель
Сommit
99e856b8d9

+ 20 - 1
packages/app/src/server/models/user-group.js

@@ -63,7 +63,7 @@ class UserGroup {
    * @memberof UserGroup
    * @memberof UserGroup
    */
    */
   static findUserGroupsWithPagination(opts) {
   static findUserGroupsWithPagination(opts) {
-    const query = {};
+    const query = { parent: null };
     const options = Object.assign({}, opts);
     const options = Object.assign({}, opts);
     if (options.page == null) {
     if (options.page == null) {
       options.page = 1;
       options.page = 1;
@@ -78,6 +78,25 @@ class UserGroup {
       });
       });
   }
   }
 
 
+  static async findChildUserGroupsByParentIds(parentIds, includeGrandChildren = false) {
+    if (!Array.isArray(parentIds)) {
+      throw Error('parentIds must be an array.');
+    }
+
+    const childUserGroups = await this.find({ parent: { $in: parentIds } });
+
+    let grandChildUserGroups = null;
+    if (includeGrandChildren) {
+      const childUserGroupIds = childUserGroups.map(group => group._id);
+      grandChildUserGroups = await this.find({ parent: { $in: childUserGroupIds } });
+    }
+
+    return {
+      childUserGroups,
+      grandChildUserGroups,
+    };
+  }
+
   // Check if registerable
   // Check if registerable
   static isRegisterableName(name) {
   static isRegisterableName(name) {
     const query = { name };
     const query = { name };

+ 19 - 61
packages/app/src/server/routes/apiv3/user-group-relation.js

@@ -3,12 +3,15 @@ import loggerFactory from '~/utils/logger';
 const logger = loggerFactory('growi:routes:apiv3:user-group-relation'); // eslint-disable-line no-unused-vars
 const logger = loggerFactory('growi:routes:apiv3:user-group-relation'); // eslint-disable-line no-unused-vars
 
 
 const express = require('express');
 const express = require('express');
+const { query } = require('express-validator');
 
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const { serializeUserGroupRelationSecurely } = require('../../models/serializers/user-group-relation-serializer');
 const { serializeUserGroupRelationSecurely } = require('../../models/serializers/user-group-relation-serializer');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
+const validator = {};
+
 /**
 /**
  * @swagger
  * @swagger
  *  tags:
  *  tags:
@@ -21,6 +24,11 @@ module.exports = (crowi) => {
 
 
   const { UserGroupRelation } = crowi.models;
   const { UserGroupRelation } = crowi.models;
 
 
+  validator.list = [
+    query('groupIds', 'groupIds is required and must be an array').isArray(),
+    query('childGroupIds', 'childGroupIds must be an array').optional().isArray(),
+  ];
+
   /**
   /**
    * @swagger
    * @swagger
    *  paths:
    *  paths:
@@ -41,13 +49,21 @@ module.exports = (crowi) => {
    *                      type: object
    *                      type: object
    *                      description: contains arrays user objects related
    *                      description: contains arrays user objects related
    */
    */
-  router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { // TODO 85062: enable groupIds to filter
+  router.get('/', loginRequiredStrictly, adminRequired, validator.list, async(req, res) => {
+    const { query } = req;
+
     try {
     try {
-      const relations = await UserGroupRelation.find().populate('relatedUser');
+      const relations = await UserGroupRelation.find({ relatedGroup: { $in: query.groupIds } }).populate('relatedUser');
+
+      let relationsOfChildGroups = null;
+      if (Array.isArray(query.childGroupIds)) {
+        const _relationsOfChildGroups = await UserGroupRelation.find({ relatedGroup: { $in: query.childGroupIds } }).populate('relatedUser');
+        relationsOfChildGroups = _relationsOfChildGroups.map(relation => serializeUserGroupRelationSecurely(relation)); // serialize
+      }
 
 
       const serialized = relations.map(relation => serializeUserGroupRelationSecurely(relation));
       const serialized = relations.map(relation => serializeUserGroupRelationSecurely(relation));
 
 
-      return res.apiv3({ userGroupRelations: serialized });
+      return res.apiv3({ userGroupRelations: serialized, relationsOfChildGroups });
     }
     }
     catch (err) {
     catch (err) {
       const msg = 'Error occurred in fetching user group relations';
       const msg = 'Error occurred in fetching user group relations';
@@ -58,61 +74,3 @@ module.exports = (crowi) => {
 
 
   return router;
   return router;
 };
 };
-
-// const MAX_PAGE_LIST = 50;
-
-// function createPager(total, limit, page, pagesCount, maxPageList) {
-//   const pager = {
-//     page,
-//     pagesCount,
-//     pages: [],
-//     total,
-//     previous: null,
-//     previousDots: false,
-//     next: null,
-//     nextDots: false,
-//   };
-
-//   if (page > 1) {
-//     pager.previous = page - 1;
-//   }
-
-//   if (page < pagesCount) {
-//     pager.next = page + 1;
-//   }
-
-//   let pagerMin = Math.max(1, Math.ceil(page - maxPageList / 2));
-//   let pagerMax = Math.min(pagesCount, Math.floor(page + maxPageList / 2));
-//   if (pagerMin === 1) {
-//     if (MAX_PAGE_LIST < pagesCount) {
-//       pagerMax = MAX_PAGE_LIST;
-//     }
-//     else {
-//       pagerMax = pagesCount;
-//     }
-//   }
-//   if (pagerMax === pagesCount) {
-//     if ((pagerMax - MAX_PAGE_LIST) < 1) {
-//       pagerMin = 1;
-//     }
-//     else {
-//       pagerMin = pagerMax - MAX_PAGE_LIST;
-//     }
-//   }
-
-//   pager.previousDots = null;
-//   if (pagerMin > 1) {
-//     pager.previousDots = true;
-//   }
-
-//   pager.nextDots = null;
-//   if (pagerMax < pagesCount) {
-//     pager.nextDots = true;
-//   }
-
-//   for (let i = pagerMin; i <= pagerMax; i++) {
-//     pager.pages.push(i);
-//   }
-
-//   return pager;
-// }

+ 23 - 5
packages/app/src/server/routes/apiv3/user-group.js

@@ -40,6 +40,11 @@ module.exports = (crowi) => {
     Page,
     Page,
   } = crowi.models;
   } = crowi.models;
 
 
+  validator.listChildren = [
+    query('parentIds', 'parentIds must be an array').optional().isArray(),
+    query('includeGrandChildren', 'parentIds must be boolean').optional().isBoolean(),
+  ];
+
   /**
   /**
    * @swagger
    * @swagger
    *
    *
@@ -64,7 +69,7 @@ module.exports = (crowi) => {
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { // TODO 85062: userGroups with no parent
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => { // TODO 85062: userGroups with no parent
     const { query } = req;
     const { query } = req;
 
 
-    // TODO: filter with querystring
+    // TODO 85062: improve sort
     try {
     try {
       const page = query.page != null ? parseInt(query.page) : undefined;
       const page = query.page != null ? parseInt(query.page) : undefined;
       const limit = query.limit != null ? parseInt(query.limit) : undefined;
       const limit = query.limit != null ? parseInt(query.limit) : undefined;
@@ -84,10 +89,23 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
-  /*
-   * TODO 85062: GET /children ?include-grand-children=boolean fetch all children by parent ids
-   * if include-grand-children=true, return grand children as well
-   */
+  // TODO 85062: improve sort
+  router.get('/children', loginRequiredStrictly, adminRequired, validator.listChildren, async(req, res) => {
+    try {
+      const { parentIds, includeGrandChildren = false } = req.query;
+
+      const userGroupsResult = await UserGroup.findChildUserGroupsByParentIds(parentIds, includeGrandChildren);
+      return res.apiv3({
+        childUserGroups: userGroupsResult.childUserGroups,
+        grandChildUserGroups: userGroupsResult.grandChildUserGroups,
+      });
+    }
+    catch (err) {
+      const msg = 'Error occurred in fetching child user group list';
+      logger.error(msg, err);
+      return res.apiv3Err(new ErrorV3(msg, 'child-user-group-list-fetch-failed'));
+    }
+  });
 
 
   validator.create = [
   validator.create = [
     body('name', 'Group name is required').trim().exists({ checkFalsy: true }),
     body('name', 'Group name is required').trim().exists({ checkFalsy: true }),

+ 10 - 6
packages/app/src/stores/user-group.tsx

@@ -17,11 +17,13 @@ export const useSWRxUserGroupList = (initialData?: IUserGroupHasId[]): SWRRespon
 };
 };
 
 
 export const useSWRxChildUserGroupList = (
 export const useSWRxChildUserGroupList = (
-    parentIds: string[] | undefined, initialData?: IUserGroupHasId[],
+    parentIds: string[] | undefined, includeGrandChildren?: boolean, initialData?: IUserGroupHasId[],
 ): SWRResponse<IUserGroupHasId[], Error> => {
 ): SWRResponse<IUserGroupHasId[], Error> => {
   return useSWRImmutable<IUserGroupHasId[], Error>(
   return useSWRImmutable<IUserGroupHasId[], Error>(
-    parentIds != null ? ['/user-groups/children', parentIds] : null,
-    (endpoint, parentIds) => apiv3Get<ChildUserGroupListResult>(endpoint, { parentIds }).then(result => result.data.childUserGroups),
+    parentIds != null ? ['/user-groups/children', parentIds, includeGrandChildren] : null,
+    (endpoint, parentIds, includeGrandChildren) => apiv3Get<ChildUserGroupListResult>(
+      endpoint, { parentIds, includeGrandChildren },
+    ).then(result => result.data.childUserGroups),
     {
     {
       fallbackData: initialData,
       fallbackData: initialData,
     },
     },
@@ -29,11 +31,13 @@ export const useSWRxChildUserGroupList = (
 };
 };
 
 
 export const useSWRxUserGroupRelationList = (
 export const useSWRxUserGroupRelationList = (
-    groupIds: string[] | undefined, initialData?: IUserGroupRelationHasId[],
+    groupIds: string[] | undefined, childGroupIds?: string[], initialData?: IUserGroupRelationHasId[],
 ): SWRResponse<IUserGroupRelationHasId[], Error> => {
 ): SWRResponse<IUserGroupRelationHasId[], Error> => {
   return useSWRImmutable<IUserGroupRelationHasId[], Error>(
   return useSWRImmutable<IUserGroupRelationHasId[], Error>(
-    groupIds != null ? ['/user-group-relations', groupIds] : null,
-    (endpoint, parentIds) => apiv3Get<UserGroupRelationListResult>(endpoint, { parentIds }).then(result => result.data.userGroupRelations),
+    groupIds != null ? ['/user-group-relations', groupIds, childGroupIds] : null,
+    (endpoint, parentIds, childGroupIds) => apiv3Get<UserGroupRelationListResult>(
+      endpoint, { parentIds, childGroupIds },
+    ).then(result => result.data.userGroupRelations),
     {
     {
       fallbackData: initialData,
       fallbackData: initialData,
     },
     },