Procházet zdrojové kódy

Merge pull request #5867 from weseek/feat/fix-grant-alert-show-grant-in-modal

feat: fix grant alert show grant in modal
yuken před 3 roky
rodič
revize
7485761373

+ 6 - 0
packages/app/resource/locales/en_US/translation.json

@@ -1049,6 +1049,12 @@
     "modal": {
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
       "need_to_fix_grant": "The permissions associated with this page must be modified in order to use the functionality correctly. <br> Please select from the options below to make the change.",
+      "grant_label": {
+        "isForbidden": "Authority not allowed to view",
+        "currentPageGrantLabel": "Authorization for this page: ",
+        "parentPageGrantLabel": "Authority of parent page: ",
+        "docLink": "For more information on modifying permissions, please refer to <a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちらのリンク</a>"
+      },
       "radio_btn": {
         "restrected": "Only those who know the link",
         "only_me": "only to oneself",

+ 6 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -1042,6 +1042,12 @@
     "modal": {
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
       "need_to_fix_grant": "正しく機能を使用するためにはこのページに紐づく権限を修正する必要があります。 <br> 下記の選択肢から選んで変更してください。",
+      "grant_label": {
+        "isForbidden": "権限の閲覧が許可されていません",
+        "currentPageGrantLabel": "このページの権限: ",
+        "parentPageGrantLabel": "親のページの権限: ",
+        "docLink": "権限の修正についての詳細は<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちらのリンク</a>を参照してください"
+      },
       "radio_btn": {
         "restrected": "リンクを知っている人のみ",
         "only_me": "自分のみ",

+ 6 - 0
packages/app/resource/locales/zh_CN/translation.json

@@ -1052,6 +1052,12 @@
     "modal": {
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
       "need_to_fix_grant": "为了正确使用该功能,需要修改与该页面相关的权限。 <br> 请从以下选项中选择进行更改。",
+      "grant_label": {
+        "isForbidden": "无权查看的机构",
+        "currentPageGrantLabel": "本页的权限: ",
+        "parentPageGrantLabel": "父页的权限: ",
+        "docLink": "关于修改授权的更多信息,请参见此<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>此链接</a>"
+      },
       "radio_btn": {
         "restrected": "只有那些知道链接的人",
         "only_me": "只对自己说",

+ 50 - 4
packages/app/src/components/Page/FixPageGrantAlert.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
 
 import { useTranslation } from 'react-i18next';
 import {
@@ -7,8 +7,8 @@ import {
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
-import { PageGrant } from '~/interfaces/page';
-import { IRecordApplicableGrant } from '~/interfaces/page-grant';
+import { PageGrant, IPageGrantData } from '~/interfaces/page';
+import { IRecordApplicableGrant, IResIsGrantNormalizedGrantData } from '~/interfaces/page-grant';
 import { useCurrentPageId, useHasParent } from '~/stores/context';
 import { useSWRxApplicableGrant, useSWRxIsGrantNormalized } from '~/stores/page';
 
@@ -16,6 +16,7 @@ type ModalProps = {
   isOpen: boolean
   pageId: string
   dataApplicableGrant: IRecordApplicableGrant
+  currentAndParentPageGrantData: IResIsGrantNormalizedGrantData
   close(): void
 }
 
@@ -23,7 +24,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
   const { t } = useTranslation();
 
   const {
-    isOpen, pageId, dataApplicableGrant, close,
+    isOpen, pageId, dataApplicableGrant, currentAndParentPageGrantData, close,
   } = props;
 
   const [selectedGrant, setSelectedGrant] = useState<PageGrant>(PageGrant.GRANT_OWNER);
@@ -65,6 +66,46 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
     }
   };
 
+  const getGrantLabel = useCallback((isForbidden: boolean, grantData?: IPageGrantData): string => {
+
+    if (isForbidden) {
+      return t('fix_page_grant.modal.grant_label.isForbidden');
+    }
+
+    if (grantData == null) {
+      return t('fix_page_grant.modal.grant_label.isForbidden');
+    }
+
+    if (grantData.grant === 4) {
+      return t('fix_page_grant.modal.radio_btn.only_me');
+    }
+
+    if (grantData.grant === 5) {
+      if (grantData.grantedGroup == null) {
+        return t('fix_page_grant.modal.grant_label.isForbidden');
+      }
+      return `${t('fix_page_grant.modal.radio_btn.grant_group')}: (${grantData.grantedGroup.name})`;
+    }
+
+    throw Error('cannnot get grant label'); // this error can't be throwed
+  }, [t]);
+
+  const renderGrantDataLabel = useCallback(() => {
+    const { isForbidden, currentPageGrant, parentPageGrant } = currentAndParentPageGrantData;
+
+    const currentGrantLabel = getGrantLabel(false, currentPageGrant);
+    const parentGrantLabel = getGrantLabel(isForbidden, parentPageGrant);
+
+    return (
+      <>
+        <p className="mt-3">{ t('fix_page_grant.modal.grant_label.parentPageGrantLabel') + parentGrantLabel }</p>
+        <p>{ t('fix_page_grant.modal.grant_label.currentPageGrantLabel') + currentGrantLabel }</p>
+        {/* eslint-disable-next-line react/no-danger */}
+        <p dangerouslySetInnerHTML={{ __html: t('fix_page_grant.modal.grant_label.docLink') }} />
+      </>
+    );
+  }, [t, currentAndParentPageGrantData, getGrantLabel]);
+
   const renderModalBodyAndFooter = () => {
     const isGrantAvailable = Object.keys(dataApplicableGrant || {}).length > 0;
 
@@ -82,6 +123,10 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
           <div className="form-group">
             {/* eslint-disable-next-line react/no-danger */}
             <p className="mb-2" dangerouslySetInnerHTML={{ __html: t('fix_page_grant.modal.need_to_fix_grant') }} />
+
+            {/* grant data label */}
+            {renderGrantDataLabel()}
+
             <div className="ml-2">
               <div className="custom-control custom-radio mb-3">
                 <input
@@ -218,6 +263,7 @@ const FixPageGrantAlert = (): JSX.Element => {
             isOpen={isOpen}
             pageId={pageId}
             dataApplicableGrant={dataApplicableGrant}
+            currentAndParentPageGrantData={dataIsGrantNormalized.grantData}
             close={() => setOpen(false)}
           />
         )

+ 10 - 1
packages/app/src/interfaces/page-grant.ts

@@ -1,4 +1,4 @@
-import { PageGrant } from './page';
+import { PageGrant, IPageGrantData } from './page';
 
 export type IDataApplicableGroup = {
   applicableGroups?: {_id: string, name: string}[] // TODO: Typescriptize model
@@ -9,3 +9,12 @@ export type IRecordApplicableGrant = Record<PageGrant, IDataApplicableGrant>
 export type IResApplicableGrant = {
   data?: IRecordApplicableGrant
 }
+export type IResIsGrantNormalizedGrantData = {
+  isForbidden: boolean,
+  currentPageGrant: IPageGrantData,
+  parentPageGrant?: IPageGrantData
+}
+export type IResIsGrantNormalized = {
+  isGrantNormalized: boolean,
+  grantData: IResIsGrantNormalizedGrantData
+};

+ 11 - 3
packages/app/src/interfaces/page.ts

@@ -1,9 +1,9 @@
 import { Ref, Nullable } from './common';
-import { IUser } from './user';
-import { IRevision, HasRevisionShortbody } from './revision';
-import { ITag } from './tag';
 import { HasObjectId } from './has-object-id';
+import { IRevision, HasRevisionShortbody } from './revision';
 import { SubscriptionStatusType } from './subscription';
+import { ITag } from './tag';
+import { IUser } from './user';
 
 
 export interface IPage {
@@ -117,6 +117,14 @@ export type IPageWithMeta<M = IPageInfoAll> = IDataWithMeta<IPageHasId, M>;
 export type IPageToDeleteWithMeta = IDataWithMeta<HasObjectId & (IPage | { path: string, revision: string }), IPageInfoForEntity | unknown>;
 export type IPageToRenameWithMeta = IPageToDeleteWithMeta;
 
+export type IPageGrantData = {
+  grant: number,
+  grantedGroup?: {
+    id: string,
+    name: string
+  }
+}
+
 export type IDeleteSinglePageApiv1Result = {
   ok: boolean
   path: string,

+ 52 - 1
packages/app/src/server/routes/apiv3/page.js

@@ -2,6 +2,7 @@ import { pagePathUtils } from '@growi/core';
 
 import { AllSubscriptionStatusType } from '~/interfaces/subscription';
 import Subscription from '~/server/models/subscription';
+import UserGroup from '~/server/models/user-group';
 import loggerFactory from '~/utils/logger';
 
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
@@ -443,7 +444,57 @@ module.exports = (crowi) => {
       return res.apiv3Err(err, 500);
     }
 
-    return res.apiv3({ isGrantNormalized });
+    const currentPageUserGroup = await UserGroup.findOne({ _id: grantedGroup });
+    const currentPageGrant = {
+      grant,
+      grantedGroup: currentPageUserGroup != null
+        ? {
+          id: currentPageUserGroup._id,
+          name: currentPageUserGroup.name,
+        }
+        : null,
+    };
+
+    // page doesn't have parent page
+    if (page.parent == null) {
+      const grantData = {
+        isForbidden: false,
+        currentPageGrant,
+        parentPageGrant: null,
+      };
+      return res.apiv3({ isGrantNormalized, grantData });
+    }
+
+    const parentPage = await Page.findByIdAndViewer(page.parent, req.user, null, false);
+
+    // user isn't allowed to see parent's grant
+    if (parentPage == null) {
+      const grantData = {
+        isForbidden: true,
+        currentPageGrant,
+        parentPageGrant: null,
+      };
+      return res.apiv3({ isGrantNormalized, grantData });
+    }
+
+    const parentPageUserGroup = await UserGroup.findOne({ _id: parentPage.grantedGroup });
+    const parentPageGrant = {
+      grant: parentPage.grant,
+      grantedGroup: parentPageUserGroup != null
+        ? {
+          id: parentPageUserGroup._id,
+          name: parentPageUserGroup.name,
+        }
+        : null,
+    };
+
+    const grantData = {
+      isForbidden: false,
+      currentPageGrant,
+      parentPageGrant,
+    };
+
+    return res.apiv3({ isGrantNormalized, grantData });
   });
 
   router.get('/applicable-grant', loginRequiredStrictly, validator.applicableGrant, apiV3FormValidator, async(req, res) => {

+ 1 - 2
packages/app/src/stores/page.tsx

@@ -7,7 +7,7 @@ import { HasObjectId } from '~/interfaces/has-object-id';
 import {
   IPageInfo, IPageHasId, IPageInfoForOperation, IPageInfoForListing, IDataWithMeta,
 } from '~/interfaces/page';
-import { IRecordApplicableGrant } from '~/interfaces/page-grant';
+import { IRecordApplicableGrant, IResIsGrantNormalized } from '~/interfaces/page-grant';
 import { IPagingResult } from '~/interfaces/paging-result';
 
 import { apiGet } from '../client/util/apiv1-client';
@@ -151,7 +151,6 @@ export const useSWRxPageInfoForList = (
 /*
  * Grant normalization fetching hooks
  */
-export type IResIsGrantNormalized = { isGrantNormalized: boolean };
 export const useSWRxIsGrantNormalized = (
     pageId: string | null | undefined,
 ): SWRResponse<IResIsGrantNormalized, Error> => {