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

Merge branch 'feat/6972-replace-code-and-frontend' into feat/6450-insurance-branch

zahmis 4 лет назад
Родитель
Сommit
bf429ce4c9

+ 7 - 0
packages/app/resource/locales/en_US/admin/admin.json

@@ -340,6 +340,13 @@
       "manage_commands": "Manage GROWI commands",
       "multiple_growi_command": "Commands that could be sent to multiple GROWI instances at once",
       "single_growi_command": "Commands that could be sent to single GROWI instance at a time",
+      "allowed_channels_description": "Input allowed channels for \"{{commandName}}\" command. Separate each channel with \",\" . Users can will be able to use \"{{commandName}}\" command from channels written here.",
+      "allow_all": "Allow all",
+      "deny_all": "Deny all",
+      "allow_specified": "Allow specified",
+      "allow_all_long": "Allow all (The command is allowed from any channel)",
+      "deny_all_long": "Deny all (The command is denied from any channel)",
+      "allow_specified_long": "Allow specified (The command is allowed from only specified channels)",
       "test_connection": "Test Connection",
       "test_connection_by_pressing_button": "Press the button to test the connection",
       "error_check_logs_below": "An error has occurred. Please check the logs below.",

+ 7 - 0
packages/app/resource/locales/ja_JP/admin/admin.json

@@ -333,6 +333,13 @@
       "manage_commands": "使用可能なGROWIコマンドを設定する",
       "multiple_growi_command": "複数のGROWIに対して送信できるコマンド",
       "single_growi_command": "一つのGROWIに対して送信できるコマンド",
+      "allowed_channels_description": "\"{{commandName}}\" コマンドの使用を許可するチャンネルを \",\" 区切りで入力してください。ユーザーはここに記入されているチャンネルから \"{{commandName}}\" コマンドを使用することができます。",
+      "allow_all": "全てのチャンネルを許可",
+      "deny_all": "全てのチャンネルを拒否",
+      "allow_specified": "特定のチャンネルを許可",
+      "allow_all-long": "全て許可 (このコマンドは全てのチャンネルから使用することができます)",
+      "deny_all-long": "全て拒否 (このコマンドはどのチャンネルからも使用することはできません)",
+      "allow_specified-long": "特定のチャンネルを許可 (テキストボックスに入力されたチャンネルのみ許可されます)",
       "test_connection": "連携状況のテストをする",
       "test_connection_by_pressing_button": "以下のテストボタンを押して、Slack連携が完了しているかの確認をしましょう",
       "error_check_logs_below": "エラーが発生しました。下記のログを確認してください。",

+ 7 - 0
packages/app/resource/locales/zh_CN/admin/admin.json

@@ -343,6 +343,13 @@
       "manage_commands": "管理 GROWI 命令",
       "multiple_growi_command": "可以一次发送到多个 GROWI 实例的命令",
       "single_growi_command": "可以一次发送到一个 GROWI 实例的命令",
+      "allowed_channels_description": "为 \"{{commandName}}\" 命令输入允许的通道。每个通道之间用 \",\" 隔开。用户可以从这里写入的通道中使用 \"{{commandName}}\"。",
+      "allow_all": "允许所有",
+      "deny_all": "拒绝所有",
+      "allow_specified": "允许指定",
+      "allow_all_long": "允许所有(允许从任何通道发出命令)",
+      "deny_all_long": "拒绝所有(该命令被拒绝于任何通道)",
+      "allow_specified_long": "允许指定(该命令只允许来自指定的通道)",
       "test_connection": "测试连接",
       "test_connection_by_pressing_button": "按下按钮以测试连接",
       "error_check_logs_below": "发生了错误。请检查以下日志。",

+ 3 - 3
packages/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -127,7 +127,7 @@ const CustomBotWithProxySettings = (props) => {
       <div className="mx-3">
         {slackAppIntegrations.map((slackAppIntegration, i) => {
           const {
-            tokenGtoP, tokenPtoG, _id, supportedCommandsForBroadcastUse, supportedCommandsForSingleUse,
+            tokenGtoP, tokenPtoG, _id, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands,
           } = slackAppIntegration;
           const workspaceName = connectionStatuses[_id]?.workspaceName;
           return (
@@ -148,8 +148,8 @@ const CustomBotWithProxySettings = (props) => {
                 slackAppIntegrationId={slackAppIntegration._id}
                 tokenGtoP={tokenGtoP}
                 tokenPtoG={tokenPtoG}
-                supportedCommandsForBroadcastUse={supportedCommandsForBroadcastUse}
-                supportedCommandsForSingleUse={supportedCommandsForSingleUse}
+                permissionsForBroadcastUseCommands={permissionsForBroadcastUseCommands}
+                permissionsForSingleUseCommands={permissionsForSingleUseCommands}
                 onUpdateTokens={onUpdateTokens}
                 onSubmitForm={onSubmitForm}
               />

+ 234 - 92
packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcess.jsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
@@ -8,52 +8,132 @@ import { toastSuccess, toastError } from '../../../client/util/apiNotification';
 
 const logger = loggerFactory('growi:SlackIntegration:ManageCommandsProcess');
 
+const PermissionTypes = {
+  ALLOW_ALL: 'allowAll',
+  DENY_ALL: 'denyAll',
+  ALLOW_SPECIFIED: 'allowSpecified',
+};
+
+const CommandUsageTypes = {
+  BROADCAST_USE: 'broadcastUse',
+  SINGLE_USE: 'singleUse',
+};
+
+// A utility function that returns the new state but identical to the previous state
+const getUpdatedChannelsList = (prevState, commandName, value) => {
+  // string to array
+  const allowedChannelsArray = value.split(',');
+  // trim whitespace from all elements
+  const trimedAllowedChannelsArray = allowedChannelsArray.map(channelName => channelName.trim());
+
+  prevState[commandName] = trimedAllowedChannelsArray;
+  return prevState;
+};
+
+// A utility function that returns the new state
+const getUpdatedPermissionSettings = (prevState, commandName, value) => {
+  const newState = { ...prevState };
+  switch (value) {
+    case PermissionTypes.ALLOW_ALL:
+      newState[commandName] = true;
+      break;
+    case PermissionTypes.DENY_ALL:
+      newState[commandName] = false;
+      break;
+    case PermissionTypes.ALLOW_SPECIFIED:
+      newState[commandName] = [];
+      break;
+    default:
+      logger.error('Not implemented');
+      break;
+  }
+
+  return newState;
+};
+
+// A utility function that returns the permission type from the permission value
+const getPermissionTypeFromValue = (value) => {
+  if (Array.isArray(value)) {
+    return PermissionTypes.ALLOW_SPECIFIED;
+  }
+  if (typeof value === 'boolean') {
+    return value ? PermissionTypes.ALLOW_ALL : PermissionTypes.DENY_ALL;
+  }
+  logger.error('The value type must be boolean or string[]');
+};
+
+// TODO: Add permittedChannelsForEachCommand to use data from server (props must have it) GW-7006
 const ManageCommandsProcess = ({
-  apiv3Put, slackAppIntegrationId, supportedCommandsForBroadcastUse, supportedCommandsForSingleUse,
+  apiv3Put, slackAppIntegrationId, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands,
 }) => {
   const { t } = useTranslation();
-  const [selectedCommandsForBroadcastUse, setSelectedCommandsForBroadcastUse] = useState(new Set(supportedCommandsForBroadcastUse));
-  const [selectedCommandsForSingleUse, setSelectedCommandsForSingleUse] = useState(new Set(supportedCommandsForSingleUse));
 
-  const toggleCheckboxForBroadcast = (e) => {
+  // TODO: use data from server GW-7006
+  const [permissionsForBroadcastUseCommandsState, setPermissionsForBroadcastUseCommandsState] = useState({
+    search: true,
+  });
+  const [permissionsForSingleUseCommandsState, setPermissionsForSingleUseCommandsState] = useState({
+    create: false,
+    togetter: [],
+  });
+  const [currentPermissionTypes, setCurrentPermissionTypes] = useState(() => {
+    const initialState = {};
+    Object.entries(permissionsForBroadcastUseCommandsState).forEach((entry) => {
+      const [commandName, value] = entry;
+      initialState[commandName] = getPermissionTypeFromValue(value);
+    });
+    Object.entries(permissionsForSingleUseCommandsState).forEach((entry) => {
+      const [commandName, value] = entry;
+      initialState[commandName] = getPermissionTypeFromValue(value);
+    });
+    return initialState;
+  });
+
+  const updatePermissionsForBroadcastUseCommandsState = useCallback((e) => {
     const { target } = e;
-    const { name, checked } = target;
-
-    setSelectedCommandsForBroadcastUse((prevState) => {
-      const selectedCommands = new Set(prevState);
-      if (checked) {
-        selectedCommands.add(name);
-      }
-      else {
-        selectedCommands.delete(name);
-      }
-
-      return selectedCommands;
+    const { name: commandName, value } = target;
+
+    // update state
+    setPermissionsForBroadcastUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
+    setCurrentPermissionTypes((prevState) => {
+      const newState = { ...prevState };
+      newState[commandName] = value;
+      return newState;
     });
-  };
+  }, []);
 
-  const toggleCheckboxForSingleUse = (e) => {
+  const updatePermissionsForSingleUseCommandsState = useCallback((e) => {
     const { target } = e;
-    const { name, checked } = target;
-
-    setSelectedCommandsForSingleUse((prevState) => {
-      const selectedCommands = new Set(prevState);
-      if (checked) {
-        selectedCommands.add(name);
-      }
-      else {
-        selectedCommands.delete(name);
-      }
-
-      return selectedCommands;
+    const { name: commandName, value } = target;
+
+    // update state
+    setPermissionsForSingleUseCommandsState(prev => getUpdatedPermissionSettings(prev, commandName, value));
+    setCurrentPermissionTypes((prevState) => {
+      const newState = { ...prevState };
+      newState[commandName] = value;
+      return newState;
     });
-  };
+  }, []);
+
+  const updateChannelsListForBroadcastUseCommandsState = useCallback((e) => {
+    const { target } = e;
+    const { name: commandName, value } = target;
+    // update state
+    setPermissionsForBroadcastUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
+  }, []);
+
+  const updateChannelsListForSingleUseCommandsState = useCallback((e) => {
+    const { target } = e;
+    const { name: commandName, value } = target;
+    // update state
+    setPermissionsForSingleUseCommandsState(prev => getUpdatedChannelsList(prev, commandName, value));
+  }, []);
 
-  const updateCommandsHandler = async() => {
+  const updateCommandsHandler = async(e) => {
     try {
-      await apiv3Put(`/slack-integration-settings/slack-app-integrations/${slackAppIntegrationId}/supported-commands`, {
-        supportedCommandsForBroadcastUse: Array.from(selectedCommandsForBroadcastUse),
-        supportedCommandsForSingleUse: Array.from(selectedCommandsForSingleUse),
+      await apiv3Put(`/slack-integration-settings/${slackAppIntegrationId}/supported-commands`, {
+        permissionsForBroadcastUseCommands: permissionsForBroadcastUseCommandsState,
+        permissionsForSingleUseCommands: permissionsForSingleUseCommandsState,
       });
       toastSuccess(t('toaster.update_successed', { target: 'Token' }));
     }
@@ -63,69 +143,131 @@ const ManageCommandsProcess = ({
     }
   };
 
+  const PermissionSettingForEachCommandComponent = ({ commandName, commandUsageType }) => {
+    const hiddenClass = currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED ? '' : 'd-none';
+    const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
 
-  return (
-    <div className="py-4 px-5">
-      <p className="mb-4 font-weight-bold">{t('admin:slack_integration.accordion.manage_commands')}</p>
-      <div className="d-flex flex-column align-items-center">
-
-        <div>
-          <p className="font-weight-bold mb-0">Multiple GROWI</p>
-          <p className="text-muted mb-2">{t('admin:slack_integration.accordion.multiple_growi_command')}</p>
-          <div className="custom-control custom-checkbox">
-            <div className="row mb-5">
-              {defaultSupportedCommandsNameForBroadcastUse.map((commandName) => {
-                const checkboxId = `${commandName}-${slackAppIntegrationId}`;
-                return (
-                  <div className="col-sm-6 my-1" key={commandName}>
-                    <input
-                      type="checkbox"
-                      className="custom-control-input"
-                      id={checkboxId}
-                      name={commandName}
-                      value={commandName}
-                      checked={selectedCommandsForBroadcastUse.has(commandName)}
-                      onChange={toggleCheckboxForBroadcast}
-                    />
-                    <label className="text-capitalize custom-control-label ml-3" htmlFor={checkboxId}>
-                      {commandName}
-                    </label>
-                  </div>
-                );
-              })}
+    const permissionSettings = isCommandBroadcastUse ? permissionsForBroadcastUseCommandsState : permissionsForSingleUseCommandsState;
+    const permission = permissionSettings[commandName];
+    if (permission === undefined) logger.error('Must be implemented');
+
+    const textareaDefaultValue = Array.isArray(permission) ? permission.join(',') : '';
+
+    return (
+      <div className="my-1 mb-2">
+        <div className="row align-items-center mb-3">
+          <p className="col my-auto text-capitalize align-middle">{commandName}</p>
+          <div className="col dropdown">
+            <button
+              className="btn btn-outline-secondary dropdown-toggle text-right col-12 col-md-auto"
+              type="button"
+              id="dropdownMenuButton"
+              data-toggle="dropdown"
+              aria-haspopup="true"
+              aria-expanded="true"
+            >
+              <span className="float-left">
+                {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_ALL
+                && t('admin:slack_integration.accordion.allow_all')}
+                {currentPermissionTypes[commandName] === PermissionTypes.DENY_ALL
+                && t('admin:slack_integration.accordion.deny_all')}
+                {currentPermissionTypes[commandName] === PermissionTypes.ALLOW_SPECIFIED
+                && t('admin:slack_integration.accordion.allow_specified')}
+              </span>
+            </button>
+            <div className="dropdown-menu">
+              <button
+                className="dropdown-item"
+                type="button"
+                name={commandName}
+                value={PermissionTypes.ALLOW_ALL}
+                onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
+              >
+                {t('admin:slack_integration.accordion.allow_all_long')}
+              </button>
+              <button
+                className="dropdown-item"
+                type="button"
+                name={commandName}
+                value={PermissionTypes.DENY_ALL}
+                onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
+              >
+                {t('admin:slack_integration.accordion.deny_all_long')}
+              </button>
+              <button
+                className="dropdown-item"
+                type="button"
+                name={commandName}
+                value={PermissionTypes.ALLOW_SPECIFIED}
+                onClick={isCommandBroadcastUse ? updatePermissionsForBroadcastUseCommandsState : updatePermissionsForSingleUseCommandsState}
+              >
+                {t('admin:slack_integration.accordion.allow_specified_long')}
+              </button>
             </div>
           </div>
+        </div>
+        <div className={`row-12 row-md-6 ${hiddenClass}`}>
+          <textarea
+            className="form-control"
+            type="textarea"
+            name={commandName}
+            defaultValue={textareaDefaultValue}
+            onChange={isCommandBroadcastUse ? updateChannelsListForBroadcastUseCommandsState : updateChannelsListForSingleUseCommandsState}
+          />
+          <p className="form-text text-muted small">
+            {t('admin:slack_integration.accordion.allowed_channels_description', { commandName })}
+            <br />
+          </p>
+        </div>
+      </div>
+    );
+  };
 
-          <p className="font-weight-bold mb-0">Single GROWI</p>
-          <p className="text-muted mb-2">{t('admin:slack_integration.accordion.single_growi_command')}</p>
-          <div className="custom-control custom-checkbox">
-            <div className="row mb-5">
-              {defaultSupportedCommandsNameForSingleUse.map((commandName) => {
-                const checkboxId = `${commandName}-${slackAppIntegrationId}`;
-                return (
-                  <div className="col-sm-6 my-1" key={commandName}>
-                    <input
-                      type="checkbox"
-                      className="custom-control-input"
-                      id={checkboxId}
-                      name={commandName}
-                      value={commandName}
-                      checked={selectedCommandsForSingleUse.has(commandName)}
-                      onChange={toggleCheckboxForSingleUse}
-                    />
-                    <label className="text-capitalize custom-control-label ml-3" htmlFor={checkboxId}>
-                      {commandName}
-                    </label>
-                  </div>
-                );
-              })}
-            </div>
+  PermissionSettingForEachCommandComponent.propTypes = {
+    commandName: PropTypes.string,
+    commandUsageType: PropTypes.string,
+  };
+
+  const PermissionSettingsForEachCommandTypeComponent = ({ commandUsageType }) => {
+    const isCommandBroadcastUse = commandUsageType === CommandUsageTypes.BROADCAST_USE;
+    const defaultCommandsName = isCommandBroadcastUse ? defaultSupportedCommandsNameForBroadcastUse : defaultSupportedCommandsNameForSingleUse;
+    return (
+      <>
+        <p className="font-weight-bold mb-0">{isCommandBroadcastUse ? 'Multiple GROWI' : 'Single GROWI'}</p>
+        <p className="text-muted mb-2">
+          {isCommandBroadcastUse ? t('admin:slack_integration.accordion.multiple_growi_command') : t('admin:slack_integration.accordion.single_growi_command')}
+        </p>
+        <div className="custom-control custom-checkbox">
+          <div className="row mb-5 d-block">
+            {defaultCommandsName.map((commandName) => {
+              // eslint-disable-next-line max-len
+              return <PermissionSettingForEachCommandComponent key={`${commandName}-component`} commandName={commandName} commandUsageType={commandUsageType} />;
+            })}
           </div>
         </div>
+      </>
+    );
+  };
+
+  PermissionSettingsForEachCommandTypeComponent.propTypes = {
+    commandUsageType: PropTypes.string,
+  };
+
+
+  return (
+    <div className="py-4 px-5">
+      <p className="mb-4 font-weight-bold">{t('admin:slack_integration.accordion.manage_commands')}</p>
+      <div className="row d-flex flex-column align-items-center">
+
+        <div className="col-8">
+          {Object.values(CommandUsageTypes).map((commandUsageType) => {
+            return <PermissionSettingsForEachCommandTypeComponent key={commandUsageType} commandUsageType={commandUsageType} />;
+          })}
+        </div>
       </div>
       <div className="row">
         <button
-          type="button"
+          type="submit"
           className="btn btn-primary mx-auto"
           onClick={updateCommandsHandler}
         >
@@ -139,8 +281,8 @@ const ManageCommandsProcess = ({
 ManageCommandsProcess.propTypes = {
   apiv3Put: PropTypes.func,
   slackAppIntegrationId: PropTypes.string.isRequired,
-  supportedCommandsForBroadcastUse: PropTypes.arrayOf(PropTypes.string),
-  supportedCommandsForSingleUse: PropTypes.arrayOf(PropTypes.string),
+  permissionsForBroadcastUseCommands: PropTypes.object.isRequired,
+  permissionsForSingleUseCommands: PropTypes.object.isRequired,
 };
 
 export default ManageCommandsProcess;

+ 3 - 3
packages/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -95,7 +95,7 @@ const OfficialBotSettings = (props) => {
       <div className="mx-3">
         {slackAppIntegrations.map((slackAppIntegration, i) => {
           const {
-            tokenGtoP, tokenPtoG, _id, supportedCommandsForBroadcastUse, supportedCommandsForSingleUse,
+            tokenGtoP, tokenPtoG, _id, permissionsForBroadcastUseCommands, permissionsForSingleUseCommands,
           } = slackAppIntegration;
           const workspaceName = connectionStatuses[_id]?.workspaceName;
           return (
@@ -116,8 +116,8 @@ const OfficialBotSettings = (props) => {
                 slackAppIntegrationId={slackAppIntegration._id}
                 tokenGtoP={tokenGtoP}
                 tokenPtoG={tokenPtoG}
-                supportedCommandsForBroadcastUse={supportedCommandsForBroadcastUse}
-                supportedCommandsForSingleUse={supportedCommandsForSingleUse}
+                permissionsForBroadcastUseCommands={permissionsForBroadcastUseCommands}
+                permissionsForSingleUseCommands={permissionsForSingleUseCommands}
                 onUpdateTokens={onUpdateTokens}
                 onSubmitForm={onSubmitForm}
               />

+ 22 - 4
packages/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -323,6 +323,15 @@ const WithProxyAccordions = (props) => {
       />,
     },
     '③': {
+      title: 'manage_commands',
+      content: <ManageCommandsProcess
+        apiv3Put={props.appContainer.apiv3.put}
+        slackAppIntegrationId={props.slackAppIntegrationId}
+        permissionsForBroadcastUseCommands={props.permissionsForBroadcastUseCommands}
+        permissionsForSingleUseCommands={props.permissionsForSingleUseCommands}
+      />,
+    },
+    '④': {
       title: 'test_connection',
       content: <TestProcess
         apiv3Post={props.appContainer.apiv3.post}
@@ -332,7 +341,7 @@ const WithProxyAccordions = (props) => {
         isLatestConnectionSuccess={isLatestConnectionSuccess}
       />,
     },
-    '': {
+    '': {
       title: 'manage_commands',
       content: <ManageCommandsProcess
         apiv3Put={props.appContainer.apiv3.put}
@@ -367,6 +376,15 @@ const WithProxyAccordions = (props) => {
       content: <RegisteringProxyUrlProcess />,
     },
     '⑤': {
+      title: 'manage_commands',
+      content: <ManageCommandsProcess
+        apiv3Put={props.appContainer.apiv3.put}
+        slackAppIntegrationId={props.slackAppIntegrationId}
+        permissionsForBroadcastUseCommands={props.permissionsForBroadcastUseCommands}
+        permissionsForSingleUseCommands={props.permissionsForSingleUseCommands}
+      />,
+    },
+    '⑥': {
       title: 'test_connection',
       content: <TestProcess
         apiv3Post={props.appContainer.apiv3.post}
@@ -376,7 +394,7 @@ const WithProxyAccordions = (props) => {
         isLatestConnectionSuccess={isLatestConnectionSuccess}
       />,
     },
-    '': {
+    '': {
       title: 'manage_commands',
       content: <ManageCommandsProcess
         apiv3Put={props.appContainer.apiv3.put}
@@ -424,8 +442,8 @@ WithProxyAccordions.propTypes = {
   slackAppIntegrationId: PropTypes.string.isRequired,
   tokenPtoG: PropTypes.string,
   tokenGtoP: PropTypes.string,
-  supportedCommandsForBroadcastUse: PropTypes.arrayOf(PropTypes.string),
-  supportedCommandsForSingleUse: PropTypes.arrayOf(PropTypes.string),
+  permissionsForBroadcastUseCommands: PropTypes.arrayOf(PropTypes.string),
+  permissionsForSingleUseCommands: PropTypes.arrayOf(PropTypes.string),
 };
 
 export default WithProxyAccordionsWrapper;