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

Merge pull request #8296 from weseek/imprv/136025-136026-disable-multiple-group-grant-to-page

imprv: Disable multiple group grant to page
Yuki Takei 2 лет назад
Родитель
Сommit
2b0c426c44

+ 29 - 61
apps/app/src/components/PageAlert/FixPageGrantAlert.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState, useCallback } from 'react';
 
-import { PageGrant, GroupType } from '@growi/core';
+import { PageGrant } from '@growi/core';
 import { useTranslation } from 'react-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
@@ -31,7 +31,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
   const [selectedGrant, setSelectedGrant] = useState<PageGrant>(PageGrant.GRANT_RESTRICTED);
 
   const [isGroupSelectModalShown, setIsGroupSelectModalShown] = useState(false);
-  const [selectedGroups, setSelectedGroups] = useState<PopulatedGrantedGroup[]>([]);
+  const [selectedGroup, setSelectedGroup] = useState<PopulatedGrantedGroup | undefined>(undefined);
 
   // Alert message state
   const [shouldShowModalAlert, setShowModalAlert] = useState<boolean>(false);
@@ -42,23 +42,14 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
   useEffect(() => {
     if (isOpen) {
       setSelectedGrant(PageGrant.GRANT_RESTRICTED);
-      setSelectedGroups([]);
+      setSelectedGroup(undefined);
       setShowModalAlert(false);
     }
   }, [isOpen]);
 
-  const groupListItemClickHandler = (group: PopulatedGrantedGroup) => {
-    if (selectedGroups.find(g => g.item._id === group.item._id) != null) {
-      setSelectedGroups(selectedGroups.filter(g => g.item._id !== group.item._id));
-    }
-    else {
-      setSelectedGroups([...selectedGroups, group]);
-    }
-  };
-
   const submit = async() => {
     // Validate input values
-    if (selectedGrant === PageGrant.GRANT_USER_GROUP && selectedGroups.length === 0) {
+    if (selectedGrant === PageGrant.GRANT_USER_GROUP && selectedGroup == null) {
       setShowModalAlert(true);
       return;
     }
@@ -68,9 +59,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
     try {
       await apiv3Put(`/page/${pageId}/grant`, {
         grant: selectedGrant,
-        grantedGroups: selectedGroups.length !== 0 ? selectedGroups.map((g) => {
-          return { item: g.item._id, type: g.type };
-        }) : null,
+        grantedGroups: selectedGroup?.item._id != null ? [{ item: selectedGroup?.item._id, type: selectedGroup.type }] : null,
       });
 
       toastSuccess(t('Successfully updated'));
@@ -102,7 +91,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
       if (grantData.grantedGroups == null || grantData.grantedGroups.length === 0) {
         return t('fix_page_grant.modal.grant_label.isForbidden');
       }
-      return `${t('fix_page_grant.modal.radio_btn.grant_group')} (${grantData.grantedGroups.map(g => g.name).join(', ')})`;
+      return `${t('fix_page_grant.modal.radio_btn.grant_group')}: (${grantData.grantedGroups[0].name})`;
     }
 
     throw Error('cannot get grant label'); // this error can't be throwed
@@ -191,17 +180,31 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
                   <button
                     type="button"
                     className="btn btn-secondary dropdown-toggle text-right w-100 border-0 shadow-none"
+                    data-toggle="dropdown"
                     disabled={selectedGrant !== PageGrant.GRANT_USER_GROUP} // disable when its radio input is not selected
-                    onClick={() => setIsGroupSelectModalShown(true)}
                   >
                     <span className="float-left ml-2">
                       {
-                        selectedGroups.length === 0
+                        selectedGroup == null
                           ? t('fix_page_grant.modal.select_group_default_text')
-                          : selectedGroups.map(g => g.item.name).join(', ')
+                          : selectedGroup.item.name
                       }
                     </span>
                   </button>
+                  <div className="dropdown-menu">
+                    {
+                      applicableGroups != null && applicableGroups.map(g => (
+                        <button
+                          key={g.item._id}
+                          className="dropdown-item"
+                          type="button"
+                          onClick={() => setSelectedGroup(g)}
+                        >
+                          {g.item.name}
+                        </button>
+                      ))
+                    }
+                  </div>
                 </div>
               </div>
               {
@@ -224,47 +227,12 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
   };
 
   return (
-    <>
-      <Modal size="lg" isOpen={isOpen} toggle={close}>
-        <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
-          { t('fix_page_grant.modal.title') }
-        </ModalHeader>
-        {renderModalBodyAndFooter()}
-      </Modal>
-      {applicableGroups != null && (
-        <Modal
-          isOpen={isGroupSelectModalShown}
-          toggle={() => setIsGroupSelectModalShown(false)}
-        >
-          <ModalHeader tag="h4" toggle={() => setIsGroupSelectModalShown(false)} className="bg-purple text-light">
-            {t('user_group.select_group')}
-          </ModalHeader>
-          <ModalBody>
-            <>
-              { applicableGroups.map((group) => {
-                const groupIsGranted = selectedGroups?.find(g => g.item._id === group.item._id) != null;
-                const activeClass = groupIsGranted ? 'active' : '';
-
-                return (
-                  <button
-                    className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
-                    type="button"
-                    key={group.item._id}
-                    onClick={() => groupListItemClickHandler(group)}
-                  >
-                    <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
-                    <h5 className="d-inline-block ml-3">{group.item.name}</h5>
-                    {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">{group.item.provider}</span>}
-                    {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
-                  </button>
-                );
-              }) }
-              <button type="button" className="btn btn-primary mt-2 float-right" onClick={() => setIsGroupSelectModalShown(false)}>{t('Done')}</button>
-            </>
-          </ModalBody>
-        </Modal>
-      )}
-    </>
+    <Modal size="lg" isOpen={isOpen} toggle={close}>
+      <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
+        { t('fix_page_grant.modal.title') }
+      </ModalHeader>
+      {renderModalBodyAndFooter()}
+    </Modal>
   );
 };
 

+ 1 - 5
apps/app/src/components/PageAlert/PageGrantAlert.tsx

@@ -14,10 +14,6 @@ export const PageGrantAlert = (): JSX.Element => {
     return <></>;
   }
 
-  const populatedGrantedGroups = () => {
-    return pageData.grantedGroups.filter(group => isPopulated(group.item));
-  };
-
   const renderAlertContent = () => {
     const getGrantLabel = () => {
       if (pageData.grant === 2) {
@@ -39,7 +35,7 @@ export const PageGrantAlert = (): JSX.Element => {
           <>
             <i className="icon-fw icon-organization"></i>
             <strong>{
-              populatedGrantedGroups().map(g => g.item.name).join(', ')
+              isPopulated(pageData.grantedGroups[0].item) && pageData.grantedGroups[0].item.name
             }
             </strong>
           </>

+ 12 - 34
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -86,17 +86,12 @@ export const GrantSelector = (props: Props): JSX.Element => {
 
   const groupListItemClickHandler = useCallback((grantGroup: IGrantedGroup) => {
     if (onUpdateGrant != null && isPopulated(grantGroup.item)) {
-      let grantedGroupsCopy = grantedGroups != null ? [...grantedGroups] : [];
-      const grantGroupInfo = { id: grantGroup.item._id, name: grantGroup.item.name, type: grantGroup.type };
-      if (grantedGroupsCopy.find(group => group.id === grantGroupInfo.id) == null) {
-        grantedGroupsCopy.push(grantGroupInfo);
-      }
-      else {
-        grantedGroupsCopy = grantedGroupsCopy.filter(group => group.id !== grantGroupInfo.id);
-      }
-      onUpdateGrant({ grant: 5, grantedGroups: grantedGroupsCopy });
+      onUpdateGrant({ grant: 5, grantedGroups: [{ id: grantGroup.item._id, name: grantGroup.item.name, type: grantGroup.type }] });
     }
-  }, [onUpdateGrant, grantedGroups]);
+
+    // hide modal
+    setIsSelectGroupModalShown(false);
+  }, [onUpdateGrant]);
 
   /**
    * Render grant selector DOM.
@@ -132,15 +127,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
       const labelElm = (
         <span>
           <i className="icon icon-fw icon-organization"></i>
-          <span className="label">
-            {grantedGroups.length > 1
-              ? (
-                <span>
-                  {`${grantedGroups[0].name}... `}
-                  <span className="badge badge-purple">+{grantedGroups.length - 1}</span>
-                </span>
-              ) : grantedGroups[0].name}
-          </span>
+          <span className="label">{grantedGroups[0].name}</span>
         </span>
       );
 
@@ -193,30 +180,20 @@ export const GrantSelector = (props: Props): JSX.Element => {
     }
 
     return (
-      <>
+      <div className="list-group">
         { myUserGroups.map((group) => {
-          const groupIsGranted = grantedGroups?.find(g => g.id === group.item._id) != null;
-          const activeClass = groupIsGranted ? 'active' : '';
-
           return (
-            <button
-              className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
-              type="button"
-              key={group.item._id}
-              onClick={() => groupListItemClickHandler(group)}
-            >
-              <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
-              <h5 className="d-inline-block ml-3">{group.item.name}</h5>
+            <button key={group.item._id} type="button" className="list-group-item list-group-item-action" onClick={() => groupListItemClickHandler(group)}>
+              <h5 className="d-inline-block">{group.item.name}</h5>
               {group.type === GroupType.externalUserGroup && <span className="ml-2 badge badge-pill badge-info">{group.item.provider}</span>}
               {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
             </button>
           );
         }) }
-        <button type="button" className="btn btn-primary mt-2 float-right" onClick={() => setIsSelectGroupModalShown(false)}>{t('Done')}</button>
-      </>
+      </div>
     );
 
-  }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t, grantedGroups]);
+  }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t]);
 
   return (
     <>
@@ -225,6 +202,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
       {/* render modal */}
       { !disabled && currentUser != null && (
         <Modal
+          className="select-grant-group"
           isOpen={isSelectGroupModalShown}
           toggle={() => setIsSelectGroupModalShown(false)}
         >

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

@@ -149,6 +149,7 @@ export const SyncExecution = ({
       </form>
 
       <Modal
+        className="select-grant-group"
         isOpen={isAlertModalOpen}
         toggle={() => setIsAlertModalOpen(false)}
       >

+ 5 - 0
apps/app/src/server/routes/apiv3/page.js

@@ -564,6 +564,11 @@ module.exports = (crowi) => {
     const { pageId } = req.params;
     const { grant, grantedGroups } = req.body;
 
+    // TODO: remove in https://redmine.weseek.co.jp/issues/136137
+    if (grantedGroups != null && grantedGroups.length > 1) {
+      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+    }
+
     const Page = crowi.model('Page');
 
     const page = await Page.findByIdAndViewer(pageId, req.user, null, false);

+ 10 - 0
apps/app/src/server/routes/apiv3/pages.js

@@ -298,6 +298,11 @@ module.exports = (crowi) => {
       body, grant, grantUserGroupIds, overwriteScopesOfDescendants, isSlackEnabled, slackChannels, pageTags,
     } = req.body;
 
+    // TODO: remove in https://redmine.weseek.co.jp/issues/136136
+    if (grantUserGroupIds != null && grantUserGroupIds.length > 1) {
+      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+    }
+
     let { path } = req.body;
 
     // check whether path starts slash
@@ -789,6 +794,11 @@ module.exports = (crowi) => {
 
       const page = await Page.findByIdAndViewer(pageId, req.user, null, true);
 
+      // TODO: remove in https://redmine.weseek.co.jp/issues/136139
+      if (page.grantedGroups != null && page.grantedGroups.length > 1) {
+        return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+      }
+
       const isEmptyAndNotRecursively = page?.isEmpty && !isRecursively;
       if (page == null || isEmptyAndNotRecursively) {
         res.code = 'Page is not found';

+ 15 - 0
apps/app/src/server/routes/page.js

@@ -330,6 +330,11 @@ module.exports = function(crowi, app) {
     const slackChannels = req.body.slackChannels || null;
     const pageTags = req.body.pageTags || undefined;
 
+    // TODO: remove in https://redmine.weseek.co.jp/issues/136136
+    if (grantUserGroupIds != null && grantUserGroupIds.length > 1) {
+      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+    }
+
     if (body === null || pagePath === null) {
       return res.json(ApiResponse.error('Parameters body and path are required.'));
     }
@@ -458,6 +463,11 @@ module.exports = function(crowi, app) {
     const isSyncRevisionToHackmd = !!req.body.isSyncRevisionToHackmd; // cast to boolean
     const pageTags = req.body.pageTags || undefined;
 
+    // TODO: remove in https://redmine.weseek.co.jp/issues/136140
+    if (grantUserGroupIds != null && grantUserGroupIds.length > 1) {
+      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+    }
+
     if (pageId === null || pageBody === null || revisionId === null) {
       return res.json(ApiResponse.error('page_id, body and revision_id are required.'));
     }
@@ -930,6 +940,11 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'));
     }
 
+    // TODO: remove in https://redmine.weseek.co.jp/issues/136139
+    if (page.grantedGroups != null && page.grantedGroups.length > 1) {
+      return res.apiv3Err('Cannot grant multiple groups to page at the moment');
+    }
+
     // check whether path starts slash
     newPagePath = pathUtils.addHeadingSlash(newPagePath);