Przeglądaj źródła

Merge pull request #3759 from weseek/fix/3724-user-group-management

Fix/3724 user group management
Yuki Takei 4 lat temu
rodzic
commit
7fb62227d2

+ 5 - 42
src/client/js/components/Admin/UserGroup/UserGroupPage.jsx

@@ -1,7 +1,6 @@
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 
-import PaginationWrapper from '../../PaginationWrapper';
 import UserGroupTable from './UserGroupTable';
 import UserGroupCreateForm from './UserGroupCreateForm';
 import UserGroupDeleteModal from './UserGroupDeleteModal';
@@ -17,17 +16,13 @@ class UserGroupPage extends React.Component {
 
     this.state = {
       userGroups: [],
-      userGroupRelations: {},
+      userGroupRelations: [],
       selectedUserGroup: undefined, // not null but undefined (to use defaultProps in UserGroupDeleteModal)
       isDeleteModalShow: false,
-      activePage: 1,
-      totalUserGroups: 0,
-      pagingLimit: Infinity,
     };
 
     this.xss = window.xss;
 
-    this.handlePage = this.handlePage.bind(this);
     this.showDeleteModal = this.showDeleteModal.bind(this);
     this.hideDeleteModal = this.hideDeleteModal.bind(this);
     this.addUserGroup = this.addUserGroup.bind(this);
@@ -101,35 +96,14 @@ class UserGroupPage extends React.Component {
     }
   }
 
-  async handlePage(selectedPage) {
-    await this.setState({ activePage: selectedPage });
-    await this.syncUserGroupAndRelations();
-  }
-
   async syncUserGroupAndRelations() {
-    let userGroups = [];
-    let userGroupRelations = {};
-    let totalUserGroups = 0;
-    let pagingLimit = Infinity;
-
     try {
-      const params = { page: this.state.activePage };
-      const responses = await Promise.all([
-        this.props.appContainer.apiv3.get('/user-groups', params),
-        this.props.appContainer.apiv3.get('/user-group-relations', params),
-      ]);
-
-      const [userGroupsRes, userGroupRelationsRes] = responses;
-      userGroups = userGroupsRes.data.userGroups;
-      totalUserGroups = userGroupsRes.data.totalUserGroups;
-      pagingLimit = userGroupsRes.data.pagingLimit;
-      userGroupRelations = userGroupRelationsRes.data.userGroupRelations;
+      const userGroupsRes = await this.props.appContainer.apiv3.get('/user-groups', { pagination: false });
+      const userGroupRelationsRes = await this.props.appContainer.apiv3.get('/user-group-relations');
 
       this.setState({
-        userGroups,
-        userGroupRelations,
-        totalUserGroups,
-        pagingLimit,
+        userGroups: userGroupsRes.data.userGroups,
+        userGroupRelations: userGroupRelationsRes.data.userGroupRelations,
       });
     }
     catch (err) {
@@ -152,17 +126,6 @@ class UserGroupPage extends React.Component {
           onDelete={this.showDeleteModal}
           userGroupRelations={this.state.userGroupRelations}
         />
-        {this.state.userGroups.length === 0
-        ? <p>No groups yet</p> : (
-          <PaginationWrapper
-            activePage={this.state.activePage}
-            changePage={this.handlePage}
-            totalItemsCount={this.state.totalUserGroups}
-            pagingLimit={this.state.pagingLimit}
-            align="center"
-            size="sm"
-          />
-        )}
         <UserGroupDeleteModal
           userGroups={this.state.userGroups}
           deleteUserGroup={this.state.selectedUserGroup}

+ 31 - 5
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -15,17 +15,41 @@ class UserGroupTable extends React.Component {
 
     this.state = {
       userGroups: this.props.userGroups,
-      userGroupRelations: this.props.userGroupRelations,
+      userGroupMap: {},
     };
 
+    this.generateUserGroupMap = this.generateUserGroupMap.bind(this);
     this.onDelete = this.onDelete.bind(this);
   }
 
+  componentWillMount() {
+    const userGroupMap = this.generateUserGroupMap(this.props.userGroups, this.props.userGroupRelations);
+    this.setState({ userGroupMap });
+  }
+
   componentWillReceiveProps(nextProps) {
+    const { userGroups, userGroupRelations } = nextProps;
+    const userGroupMap = this.generateUserGroupMap(userGroups, userGroupRelations);
+
     this.setState({
-      userGroups: nextProps.userGroups,
-      userGroupRelations: nextProps.userGroupRelations,
+      userGroups,
+      userGroupMap,
+    });
+  }
+
+  generateUserGroupMap(userGroups, userGroupRelations) {
+    const userGroupMap = {};
+    userGroupRelations.forEach((relation) => {
+      const group = relation.relatedGroup;
+
+      const users = userGroupMap[group] || [];
+      users.push(relation.relatedUser);
+
+      // register
+      userGroupMap[group] = users;
     });
+
+    return userGroupMap;
   }
 
   onDelete(e) {
@@ -56,6 +80,8 @@ class UserGroupTable extends React.Component {
           </thead>
           <tbody>
             {this.state.userGroups.map((group) => {
+              const users = this.state.userGroupMap[group._id];
+
               return (
                 <tr key={group._id}>
                   {this.props.isAclEnabled
@@ -68,7 +94,7 @@ class UserGroupTable extends React.Component {
                   }
                   <td>
                     <ul className="list-inline">
-                      {this.state.userGroupRelations[group._id].map((user) => {
+                      {users != null && users.map((user) => {
                         return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{this.xss.process(user.username)}</li>;
                       })}
                     </ul>
@@ -123,7 +149,7 @@ UserGroupTable.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   userGroups: PropTypes.arrayOf(PropTypes.object).isRequired,
-  userGroupRelations: PropTypes.object.isRequired,
+  userGroupRelations: PropTypes.arrayOf(PropTypes.object).isRequired,
   isAclEnabled: PropTypes.bool.isRequired,
   onDelete: PropTypes.func.isRequired,
 };

+ 25 - 0
src/server/models/serializers/user-group-relation-serializer.js

@@ -0,0 +1,25 @@
+const { serializeUserSecurely } = require('./user-serializer');
+
+function serializeInsecureUserAttributes(userGroupRelation) {
+  if (userGroupRelation.relatedUser != null && userGroupRelation.relatedUser._id != null) {
+    userGroupRelation.relatedUser = serializeUserSecurely(userGroupRelation.relatedUser);
+  }
+  return userGroupRelation;
+}
+
+function serializeUserGroupRelationSecurely(userGroupRelation) {
+  let serialized = userGroupRelation;
+
+  // invoke toObject if page is a model instance
+  if (userGroupRelation.toObject != null) {
+    serialized = userGroupRelation.toObject();
+  }
+
+  serializeInsecureUserAttributes(serialized);
+
+  return serialized;
+}
+
+module.exports = {
+  serializeUserGroupRelationSecurely,
+};

+ 0 - 27
src/server/models/user-group-relation.js

@@ -142,33 +142,6 @@ class UserGroupRelation {
     return relations.map((relation) => { return relation.relatedGroup });
   }
 
-  /**
-   * find all entities with pagination
-   *
-   * @see https://github.com/edwardhotchkiss/mongoose-paginate
-   *
-   * @static
-   * @param {UserGroup} userGroup
-   * @param {any} opts mongoose-paginate options object
-   * @returns {Promise<any>} mongoose-paginate result object
-   * @memberof UserGroupRelation
-   */
-  static findUserGroupRelationsWithPagination(userGroup, opts) {
-    const query = { relatedGroup: userGroup };
-    const options = Object.assign({}, opts);
-    if (options.page == null) {
-      options.page = 1;
-    }
-    if (options.limit == null) {
-      options.limit = UserGroupRelation.PAGE_ITEMS;
-    }
-
-    return this.paginate(query, options)
-      .catch((err) => {
-        debug('Error on pagination:', err);
-      });
-  }
-
   /**
    * count by related group id and related user
    *

+ 7 - 21
src/server/routes/apiv3/user-group-relation.js

@@ -4,8 +4,8 @@ const logger = loggerFactory('growi:routes:apiv3:user-group-relation'); // eslin
 
 const express = require('express');
 
-const { serializeUserSecurely } = require('../../models/serializers/user-serializer');
 const ErrorV3 = require('../../models/vo/error-apiv3');
+const { serializeUserGroupRelationSecurely } = require('../../models/serializers/user-group-relation-serializer');
 
 const router = express.Router();
 
@@ -19,7 +19,7 @@ module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
 
-  const { UserGroup, UserGroupRelation } = crowi.models;
+  const { UserGroupRelation } = crowi.models;
 
   /**
    * @swagger
@@ -42,26 +42,12 @@ module.exports = (crowi) => {
    *                      description: contains arrays user objects related
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
-    // TODO: filter with querystring? or body
     try {
-      const page = parseInt(req.query.page) || 1;
-      const result = await UserGroup.findUserGroupsWithPagination({ page });
-      // const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
-      const userGroups = result.docs;
-
-      const userGroupRelationsObj = {};
-      await Promise.all(userGroups.map(async(userGroup) => {
-        const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
-        userGroupRelationsObj[userGroup._id] = userGroupRelations.map((userGroupRelation) => {
-          return serializeUserSecurely(userGroupRelation.relatedUser);
-        });
-      }));
-
-      const data = {
-        userGroupRelations: userGroupRelationsObj,
-      };
-
-      return res.apiv3(data);
+      const relations = await UserGroupRelation.find().populate('relatedUser');
+
+      const serialized = relations.map(relation => serializeUserGroupRelationSecurely(relation));
+
+      return res.apiv3({ userGroupRelations: serialized });
     }
     catch (err) {
       const msg = 'Error occurred in fetching user group relations';

+ 10 - 2
src/server/routes/apiv3/user-group.js

@@ -62,10 +62,18 @@ module.exports = (crowi) => {
    *                      description: a result of `UserGroup.find`
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
+    const { query } = req;
+
     // TODO: filter with querystring
     try {
-      const page = parseInt(req.query.page) || 1;
-      const result = await UserGroup.findUserGroupsWithPagination({ page });
+      const page = query.page != null ? parseInt(query.page) : undefined;
+      const limit = query.limit != null ? parseInt(query.limit) : undefined;
+      const offset = query.offset != null ? parseInt(query.offset) : undefined;
+      const pagination = query.pagination != null ? query.pagination !== 'false' : undefined;
+
+      const result = await UserGroup.findUserGroupsWithPagination({
+        page, limit, offset, pagination,
+      });
       const { docs: userGroups, totalDocs: totalUserGroups, limit: pagingLimit } = result;
       return res.apiv3({ userGroups, totalUserGroups, pagingLimit });
     }