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

Merge branch 'feat/growi-bot' into imprv/set-state-signing-secret-and-token-by-hitting-api

zahmis 5 лет назад
Родитель
Сommit
5df7371ae4

+ 2 - 0
src/client/js/admin.jsx

@@ -10,6 +10,7 @@ import ErrorBoundary from './components/ErrorBoudary';
 import AdminHome from './components/Admin/AdminHome/AdminHome';
 import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
 import NotificationSetting from './components/Admin/Notification/NotificationSetting';
+import SlackIntegrationNotificationSetting from './components/Admin/Notification/SlackIntegrationNotificationSetting';
 import SlackIntegration from './components/Admin/SlackIntegration/SlackIntegration';
 import ManageGlobalNotification from './components/Admin/Notification/ManageGlobalNotification';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
@@ -98,6 +99,7 @@ Object.assign(componentMappings, {
   'admin-export-page': <ExportArchiveDataPage />,
   'admin-notification-setting': <NotificationSetting />,
   'admin-slack-integration': <SlackIntegration />,
+  'admin-slack-integration-notification-setting': <SlackIntegrationNotificationSetting />,
   'admin-global-notification-setting': <ManageGlobalNotification />,
   'admin-user-page': <UserManagement />,
   'admin-external-account-setting': <ManageExternalAccount />,

+ 13 - 14
src/client/js/components/Admin/Common/AdminNavigation.jsx

@@ -14,20 +14,19 @@ const AdminNavigation = (props) => {
   // eslint-disable-next-line react/prop-types
   const MenuLabel = ({ menu }) => {
     switch (menu) {
-      case 'app':               return <><i className="icon-fw icon-settings"></i>        { t('App Settings') }</>;
-      case 'security':          return <><i className="icon-fw icon-shield"></i>          { t('security_settings') }</>;
-      case 'markdown':          return <><i className="icon-fw icon-note"></i>            { t('Markdown Settings') }</>;
-      case 'customize':         return <><i className="icon-fw icon-wrench"></i>          { t('Customize') }</>;
-      case 'importer':          return <><i className="icon-fw icon-cloud-upload"></i>    { t('Import Data') }</>;
-      case 'export':            return <><i className="icon-fw icon-cloud-download"></i>  { t('Export Archive Data') }</>;
-      case 'notification':      return <><i className="icon-fw icon-bell"></i>            { t('External_Notification') }</>;
-      // TODO change icon for legacy-slack-integration by GW-5466
-      case 'legacy-slack-integration':  return <> <i className="icon-fw icon-paper-plane"></i>    { t('Legacy_Slack_Integration') }</>;
-      case 'slack-integration': return <><i className="icon-fw icon-paper-plane"></i>     { t('slack_integration') }</>;
-      case 'users':             return <><i className="icon-fw icon-user"></i>            { t('User_Management') }</>;
-      case 'user-groups':       return <><i className="icon-fw icon-people"></i>          { t('UserGroup Management') }</>;
-      case 'search':            return <><i className="icon-fw icon-magnifier"></i>       { t('Full Text Search Management') }</>;
-      default:                  return <><i className="icon-fw icon-home"></i>            { t('Wiki Management Home Page') }</>;
+      case 'app':                      return <><i className="icon-fw icon-settings"></i>        { t('App Settings') }</>;
+      case 'security':                 return <><i className="icon-fw icon-shield"></i>          { t('security_settings') }</>;
+      case 'markdown':                 return <><i className="icon-fw icon-note"></i>            { t('Markdown Settings') }</>;
+      case 'customize':                return <><i className="icon-fw icon-wrench"></i>          { t('Customize') }</>;
+      case 'importer':                 return <><i className="icon-fw icon-cloud-upload"></i>    { t('Import Data') }</>;
+      case 'export':                   return <><i className="icon-fw icon-cloud-download"></i>  { t('Export Archive Data') }</>;
+      case 'notification':             return <><i className="icon-fw icon-bell"></i>            { t('External_Notification')}</>;
+      case 'legacy-slack-integration': return <><i className="fa fa-slack mr-2"></i>             { t('Legacy_Slack_Integration')}</>;
+      case 'slack-integration':        return <><i className="fa fa-slack mr-2"></i>             { t('slack_integration') }</>;
+      case 'users':                    return <><i className="icon-fw icon-user"></i>            { t('User_Management') }</>;
+      case 'user-groups':              return <><i className="icon-fw icon-people"></i>          { t('UserGroup Management') }</>;
+      case 'search':                   return <><i className="icon-fw icon-magnifier"></i>       { t('Full Text Search Management') }</>;
+      default:                         return <><i className="icon-fw icon-home"></i>            { t('Wiki Management Home Page') }</>;
     }
   };
 

+ 4 - 13
src/client/js/components/Admin/Notification/NotificationSetting.jsx

@@ -13,7 +13,6 @@ import AdminNotificationContainer from '../../../services/AdminNotificationConta
 
 import { CustomNavTab } from '../../CustomNavigation/CustomNav';
 
-import SlackAppConfiguration from './SlackAppConfiguration';
 import UserTriggerNotification from './UserTriggerNotification';
 import GlobalNotification from './GlobalNotification';
 
@@ -23,8 +22,8 @@ let retrieveErrors = null;
 function NotificationSetting(props) {
   const { adminNotificationContainer } = props;
 
-  const [activeTab, setActiveTab] = useState('slack_configuration');
-  const [activeComponents, setActiveComponents] = useState(new Set(['slack_configuration']));
+  const [activeTab, setActiveTab] = useState('user_trigger_notification');
+  const [activeComponents, setActiveComponents] = useState(new Set(['user_trigger_notification']));
 
   const switchActiveTab = (selectedTab) => {
     setActiveTab(selectedTab);
@@ -52,20 +51,15 @@ function NotificationSetting(props) {
 
   const navTabMapping = useMemo(() => {
     return {
-      slack_configuration: {
-        Icon: () => <i className="icon-settings" />,
-        i18n: 'Slack configuration',
-        index: 0,
-      },
       user_trigger_notification: {
         Icon: () => <i className="icon-settings" />,
         i18n: 'User trigger notification',
-        index: 1,
+        index: 0,
       },
       global_notification: {
         Icon: () => <i className="icon-settings" />,
         i18n: 'Global notification',
-        index: 2,
+        index: 1,
       },
     };
   }, []);
@@ -75,9 +69,6 @@ function NotificationSetting(props) {
       <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
 
       <TabContent activeTab={activeTab} className="p-5">
-        <TabPane tabId="slack_configuration">
-          {activeComponents.has('slack_configuration') && <SlackAppConfiguration />}
-        </TabPane>
         <TabPane tabId="user_trigger_notification">
           {activeComponents.has('user_trigger_notification') && <UserTriggerNotification />}
         </TabPane>

+ 80 - 0
src/client/js/components/Admin/Notification/SlackIntegrationNotificationSetting.jsx

@@ -0,0 +1,80 @@
+import React, { useMemo, useState } from 'react';
+import PropTypes from 'prop-types';
+
+import loggerFactory from '@alias/logger';
+
+import { TabContent, TabPane } from 'reactstrap';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastError } from '../../../util/apiNotification';
+import toArrayIfNot from '../../../../../lib/util/toArrayIfNot';
+import { withLoadingSppiner } from '../../SuspenseUtils';
+
+import AdminNotificationContainer from '../../../services/AdminNotificationContainer';
+
+import { CustomNavTab } from '../../CustomNavigation/CustomNav';
+
+import SlackAppConfiguration from './SlackAppConfiguration';
+
+const logger = loggerFactory('growi:NotificationSetting');
+
+let retrieveErrors = null;
+function NotificationSetting(props) {
+  const { adminNotificationContainer } = props;
+
+  const [activeTab, setActiveTab] = useState('slack_configuration');
+  const [activeComponents, setActiveComponents] = useState(new Set(['slack_configuration']));
+
+  const switchActiveTab = (selectedTab) => {
+    setActiveTab(selectedTab);
+    setActiveComponents(activeComponents.add(selectedTab));
+  };
+
+  if (adminNotificationContainer.state.webhookUrl === adminNotificationContainer.dummyWebhookUrl) {
+    throw (async() => {
+      try {
+        await adminNotificationContainer.retrieveNotificationData();
+      }
+      catch (err) {
+        const errs = toArrayIfNot(err);
+        toastError(errs);
+        logger.error(errs);
+        retrieveErrors = errs;
+        adminNotificationContainer.setState({ webhookUrl: adminNotificationContainer.dummyWebhookUrlForError });
+      }
+    })();
+  }
+
+  if (adminNotificationContainer.state.webhookUrl === adminNotificationContainer.dummyWebhookUrlForError) {
+    throw new Error(`${retrieveErrors.length} errors occured`);
+  }
+
+  const navTabMapping = useMemo(() => {
+    return {
+      slack_configuration: {
+        Icon: () => <i className="icon-settings" />,
+        i18n: 'Slack configuration',
+        index: 0,
+      },
+    };
+  }, []);
+
+  return (
+    <>
+      <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
+
+      <TabContent activeTab={activeTab} className="p-5">
+        <TabPane tabId="slack_configuration">
+          {activeComponents.has('slack_configuration') && <SlackAppConfiguration />}
+        </TabPane>
+      </TabContent>
+    </>
+  );
+}
+
+const NotificationSettingWithUnstatedContainer = withUnstatedContainers(withLoadingSppiner(NotificationSetting), [AdminNotificationContainer]);
+
+NotificationSetting.propTypes = {
+  adminNotificationContainer: PropTypes.instanceOf(AdminNotificationContainer).isRequired,
+};
+
+export default NotificationSettingWithUnstatedContainer;

+ 41 - 25
src/client/js/components/Admin/SlackIntegration/CustomBotNonProxySettings.jsx

@@ -1,7 +1,6 @@
 /* eslint-disable no-console */
 import React, { useState, useEffect, useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
-import loggerFactory from '@alias/logger';
 
 import PropTypes from 'prop-types';
 
@@ -10,38 +9,55 @@ import AppContainer from '../../../services/AppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
-const logger = loggerFactory('growi:appSettings');
 
-function CustomBotNonProxySettings(props) {
+const CustomBotNonProxySettings = (props) => {
   const { appContainer } = props;
-  const { t } = useTranslation('admin');
-  const [secret, setSecret] = useState('');
-  const [token, setToken] = useState('');
-  const [secretEnv, setSecretEnv] = useState('');
-  const [tokenEnv, setTokenEnv] = useState('');
+  const { t } = useTranslation();
 
+  const [slackSigningSecret, setSlackSigningSecret] = useState('');
+  const [slackBotToken, setSlackBotToken] = useState('');
+  const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
+  const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
 
-  function updateHandler() {
-    console.log(`Signing Secret: ${secret}, Bot User OAuth Token: ${token}`);
-  }
-
+  const botType = 'non-proxy';
 
-  const getSlackBotSettingParams = useCallback(async() => {
+  const fetchData = useCallback(async() => {
     try {
-      const response = await appContainer.apiv3.get('/slack-integration');
-      setSecretEnv(response.data.slackBotSettingParams.cusotmBotNonProxySettings.slackSigningSecretEnvVars);
-      setTokenEnv(response.data.slackBotSettingParams.cusotmBotNonProxySettings.slackBotTokenEnvVars);
+      const res = await appContainer.apiv3.get('/slack-integration/');
+      const {
+        slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
+      } = res.data.slackBotSettingParams.customBotNonProxySettings;
+      setSlackSigningSecret(slackSigningSecret);
+      setSlackBotToken(slackBotToken);
+      setSlackSigningSecretEnv(slackSigningSecretEnvVars);
+      setSlackBotTokenEnv(slackBotTokenEnvVars);
     }
     catch (err) {
-      logger.error(err);
+      toastError(err);
     }
   }, [appContainer]);
-
   useEffect(() => {
-    getSlackBotSettingParams();
-  }, [getSlackBotSettingParams]);
+    fetchData();
+
+  }, [fetchData]);
+
+
+  async function updateHandler() {
+    try {
+      await appContainer.apiv3.put('/slack-integration/custom-bot-non-proxy', {
+        slackSigningSecret,
+        slackBotToken,
+        botType,
+      });
+      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_non_proxy_settings') }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
 
   return (
     <>
@@ -61,10 +77,10 @@ function CustomBotNonProxySettings(props) {
         <label className="text-left text-md-right col-md-3 col-form-label">Signing Secret</label>
         <div className="col-md-6">
           <input
-            defaultValue={secretEnv || null}
             className="form-control"
             type="text"
-            onChange={e => setSecret(e.target.value)}
+            value={slackSigningSecret || slackSigningSecretEnv || ''}
+            onChange={e => setSlackSigningSecret(e.target.value)}
           />
         </div>
       </div>
@@ -73,10 +89,10 @@ function CustomBotNonProxySettings(props) {
         <label className="text-left text-md-right col-md-3 col-form-label">Bot User OAuth Token</label>
         <div className="col-md-6">
           <input
-            defaultValue={tokenEnv || null}
             className="form-control"
             type="text"
-            onChange={e => setToken(e.target.value)}
+            value={slackBotToken || slackBotTokenEnv || ''}
+            onChange={e => setSlackBotToken(e.target.value)}
           />
         </div>
       </div>
@@ -84,7 +100,7 @@ function CustomBotNonProxySettings(props) {
       <AdminUpdateButtonRow onClick={updateHandler} disabled={false} />
     </>
   );
-}
+};
 
 const CustomBotNonProxySettingsWrapper = withUnstatedContainers(CustomBotNonProxySettings, [AppContainer]);
 

+ 2 - 2
src/server/routes/apiv3/slack-integration.js

@@ -75,7 +75,7 @@ module.exports = (crowi) => {
         // TODO impl this after GW-4939
         // AccessToken: "tempaccessdatahogehoge",
       },
-      cusotmBotNonProxySettings: {
+      customBotNonProxySettings: {
         // TODO impl this after GW-4939
         // AccessToken: "tempaccessdatahogehoge",
         slackSigningSecretEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret'),
@@ -84,7 +84,7 @@ module.exports = (crowi) => {
         slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
       },
       // TODO imple when creating with proxy
-      cusotmBotWithProxySettings: {
+      customBotWithProxySettings: {
         // TODO impl this after GW-4939
         // AccessToken: "tempaccessdatahogehoge",
       },

+ 1 - 1
src/server/views/admin/legacy-slack-integration.html

@@ -7,6 +7,6 @@
 {% endblock %}
 
 {% block content_main %}
-<!-- TODO: move contents from notification settings by GW-5467  -->
+<div id="admin-slack-integration-notification-setting" class="admin-slack-integration-notification-setting"></div>
 {% endblock content_main %}