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

Merge branch 'feat/GW-5798-enable-display-mulutiple-ws-names-and-growi-app-names' into feat/GW-5854/show-integration-card

Shun Miyazawa 4 лет назад
Родитель
Сommit
d08c0735c3

+ 24 - 2
packages/slackbot-proxy/src/controllers/slack.ts

@@ -130,7 +130,7 @@ export class SlackCtrl {
 
   @Post('/interactions')
   @UseBefore(AuthorizeInteractionMiddleware)
-  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string> {
+  async handleInteraction(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
     logger.info('receive interaction', req.body);
     logger.info('receive interaction', req.authorizeResult);
 
@@ -153,6 +153,7 @@ export class SlackCtrl {
     const { type } = payload;
 
     // register
+    // response_urls is an array but the element included is only one.
     if (type === 'view_submission' && payload.response_urls[0].action_id === 'submit_growi_url_and_access_tokens') {
       await this.registerService.upsertOrderRecord(this.orderRepository, installation, payload);
       await this.registerService.notifyServerUriToSlack(authorizeResult, payload);
@@ -162,8 +163,29 @@ export class SlackCtrl {
     /*
      * forward to GROWI server
      */
-    // TODO: forward to GROWI server by GW-5866
+    const relations = await this.relationRepository.find({ installation: installation?.id });
 
+    const promises = relations.map((relation: Relation) => {
+      // generate API URL
+      const url = new URL('/_api/v3/slack-integration/proxied/interactions', relation.growiUri);
+      return axios.post(url.toString(), {
+        ...body,
+        tokenPtoG: relation.tokenPtoG,
+      });
+    });
+
+    // pickup PromiseRejectedResult only
+    const results = await Promise.allSettled(promises);
+    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const botToken = installation?.data.bot?.token;
+
+    try {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return postEphemeralErrors(rejectedResults, body.channel_id, body.user_id, botToken!);
+    }
+    catch (err) {
+      logger.error(err);
+    }
   }
 
   @Post('/events')

+ 2 - 0
packages/slackbot-proxy/src/services/RegisterService.ts

@@ -36,6 +36,8 @@ export class RegisterService implements GrowiCommandProcessor {
           generateInputSectionBlock('growiDomain', 'GROWI domain', 'contents_input', false, 'https://example.com'),
           generateInputSectionBlock('growiAccessToken', 'GROWI ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
           generateInputSectionBlock('proxyToken', 'PROXY ACCESS_TOKEN', 'contents_input', false, 'jBMZvpk.....'),
+          // added an input block to make response_url enabled and get info (block_id, action_id, channel_id, response_url)
+          // refer to https://api.slack.com/surfaces/modals/using#modal_response_url
           {
             block_id: 'channel_to_post_proxy_url',
             type: 'input',

+ 5 - 0
resource/locales/en_US/admin/admin.json

@@ -288,8 +288,13 @@
       "discard": "Discard",
       "generate": "Generate"
     },
+    "delete": "Delete",
+    "cooperation_procedure": "Cooperation procedure",
     "official_bot_settings": "Official bot Settings",
     "custom_bot_without_proxy_settings": "Custom Bot without proxy Settings",
+    "reset": "Reset",
+    "delete_slackbot_settings": "Reset Slack Bot settings",
+    "slackbot_settings_notice": "Reset",
     "accordion": {
       "create_bot": "Create Bot",
       "how_to_create_a_bot": "How to create a bot",

+ 5 - 0
resource/locales/ja_JP/admin/admin.json

@@ -286,7 +286,12 @@
       "discard": "破棄",
       "generate": "発行"
     },
+    "delete": "削除",
+    "cooperation_procedure": "連携手順",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
+    "reset":"リセット",
+    "delete_slackbot_settings": "Slack Bot 設定をリセットする",
+    "slackbot_settings_notice": "リセットします",
     "accordion": {
       "create_bot": "Bot を作成する",
       "how_to_create_a_bot": "作成方法はこちら",

+ 5 - 0
resource/locales/zh_CN/admin/admin.json

@@ -296,7 +296,12 @@
       "discard": "丢弃",
       "generate": "生成"
     },
+    "delete": "取消",
+    "cooperation_procedure": "协作程序",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
+    "reset":"重置",
+    "delete_slackbot_settings": "重置 Slack Bot 设置",
+    "slackbot_settings_notice": "重置",
     "accordion": {
       "create_bot": "创建 Bot",
       "how_to_create_a_bot": "如何创建一个 Bot",

+ 1 - 1
src/client/js/components/Admin/SlackIntegration/AccessTokenSettings.jsx

@@ -33,7 +33,7 @@ const AccessTokenSettings = (props) => {
             {accessToken.length === 0 ? (
               <input className="form-control" type="text" value={accessToken} readOnly />
             ) : (
-              <CopyToClipboard text={accessToken} onCopy={() => toastSuccess(t('slack_integration.copied_to_clipboard'))}>
+              <CopyToClipboard text={accessToken} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
                 <input className="form-control" type="text" value={accessToken} readOnly />
               </CopyToClipboard>
             )}

+ 14 - 17
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxyIntegrationCard.jsx

@@ -11,20 +11,16 @@ const CustomBotWithProxyIntegrationCard = (props) => {
       <div className="card rounded shadow border-0 w-50 admin-bot-card">
         <h5 className="card-title font-weight-bold mt-3 ml-4">Slack</h5>
         <div className="card-body px-5">
-          {props.slackWSNameInWithProxy.length !== 0 && (
-            <>
-              {props.slackWSNameInWithProxy.map((slackWorkSpaceName) => {
-                return (
-                  <div key={slackWorkSpaceName} className="card slack-work-space-name-card">
-                    <div className="m-2 text-center">
-                      <h5 className="font-weight-bold">{slackWorkSpaceName}</h5>
-                      <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
-                    </div>
-                  </div>
-                );
-              })}
-            </>
-          )}
+          {props.slackWorkSpaceNames.map((slackWorkSpaceName) => {
+            return (
+              <div key={slackWorkSpaceName.name} className={slackWorkSpaceName.active ? 'card slack-work-space-name-card' : ''}>
+                <div className="m-2 text-center">
+                  <h5 className="font-weight-bold">{slackWorkSpaceName.name}</h5>
+                  <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
+                </div>
+              </div>
+            );
+          })}
         </div>
       </div>
 
@@ -66,9 +62,10 @@ const CustomBotWithProxyIntegrationCard = (props) => {
           </div>
         </div>
         <div className="card-body p-4 mb-5 text-center">
-          <div className="btn-group-vertical">
+          <div className="btn-group-vertical w-50">
             {props.siteNames.map((siteName) => {
-              return <a key={siteName} className="btn btn-primary mb-3">{siteName}</a>;
+              // eslint-disable-next-line max-len
+              return <button type="button" key={siteName.name} className={siteName.active ? 'btn btn-primary mb-3' : 'btn btn-outline-primary mb-3'}>{siteName.name}</button>;
             })}
           </div>
         </div>
@@ -79,7 +76,7 @@ const CustomBotWithProxyIntegrationCard = (props) => {
 
 CustomBotWithProxyIntegrationCard.propTypes = {
   siteNames: PropTypes.array,
-  slackWSNameInWithProxy: PropTypes.array,
+  slackWorkSpaceNames: PropTypes.array,
   isSlackScopeSet: PropTypes.bool,
 };
 

+ 59 - 7
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
@@ -12,20 +12,72 @@ const CustomBotWithProxySettings = (props) => {
   const { appContainer, adminAppContainer } = props;
   const { t } = useTranslation();
 
+  // TODO: Multiple accordion logic
+  const [accordionComponentsCount, setAccordionComponentsCount] = useState(0);
+  const addAccordionHandler = () => {
+    setAccordionComponentsCount(
+      prevState => prevState + 1,
+    );
+  };
+  // TODO: Delete accordion logic
+  const deleteAccordionHandler = () => {
+    setAccordionComponentsCount(
+      prevState => prevState - 1,
+    );
+  };
+
   return (
     <>
-
-      <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
+      <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
 
       {/* TODO delete tmp props */}
       <CustomBotWithProxyIntegrationCard
-        siteNames={['siteName1', 'siteName2', 'siteName3']}
-        slackWSNameInWithProxy={['wsName1', 'wsName2']}
+        siteNames={
+          [
+            { name: 'siteName1', active: true },
+            { name: 'siteName2', active: false },
+            { name: 'siteName3', active: false },
+          ]
+        }
+        slackWorkSpaceNames={
+          [
+            { name: 'wsName1', active: true },
+            { name: 'wsName2', active: false },
+          ]
+        }
         isSlackScopeSet
       />
+      <h2 className="admin-setting-header">{t('admin:slack_integration.cooperation_procedure')}</h2>
+      <div className="mx-3">
+
+        {/* // TODO: Multiple accordion logic */}
+        {Array(...Array(accordionComponentsCount)).map(i => (
+          <>
+            <div className="d-flex justify-content-end">
+              <button
+                className="my-3 btn btn-outline-danger"
+                type="button"
+                onClick={deleteAccordionHandler}
+              >
+                <i className="icon-trash mr-1" />
+                {t('admin:slack_integration.delete')}
+              </button>
+            </div>
+            <CustomBotWithProxySettingsAccordion key={i} />
+          </>
+        ))}
 
-      <div className="my-5 mx-3">
-        <CustomBotWithProxySettingsAccordion />
+        {/* TODO: Disable button when integration is incomplete */}
+        {/* TODO: i18n */}
+        <div className="row justify-content-center my-5">
+          <button
+            type="button"
+            className="btn btn-outline-primary"
+            onClick={addAccordionHandler}
+          >
+            + Slackワークスペースを追加
+          </button>
+        </div>
       </div>
     </>
   );

+ 18 - 20
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx

@@ -23,25 +23,24 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
       </div>
 
       <div className="text-center w-25">
-        {props.isSlackScopeSet && (
-          <div className="mt-5">
-            <p className="text-success small">
-              <i className="fa fa-check mr-1" />
-              {t('admin:slack_integration.integration_sentence.integration_successful')}
-            </p>
-            <hr className="align-self-center admin-border-success border-success"></hr>
-          </div>
-        )}
-        {!props.isSlackScopeSet && (
-          <div className="mt-4">
-            <small
-              className="text-secondary m-0"
-                  // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-            />
-            <hr className="align-self-center admin-border-danger border-danger"></hr>
-          </div>
-        )}
+        {/* TODO apply correct condition GW-5895 */}
+        <div className="mt-4">
+          <small
+            className="text-secondary m-0"
+                // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
+          />
+          <hr className="align-self-center admin-border-danger border-danger"></hr>
+        </div>
+
+        <div className="mt-5">
+          <p className="text-success small">
+            <i className="fa fa-check mr-1" />
+            {t('admin:slack_integration.integration_sentence.integration_successful')}
+          </p>
+          <hr className="align-self-center admin-border-success border-success"></hr>
+        </div>
+
       </div>
 
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
@@ -57,7 +56,6 @@ const CustomBotWithoutProxyIntegrationCard = (props) => {
 CustomBotWithoutProxyIntegrationCard.propTypes = {
   siteName: PropTypes.string.isRequired,
   slackWSNameInWithoutProxy: PropTypes.string,
-  isSlackScopeSet: PropTypes.bool.isRequired,
 };
 
 export default CustomBotWithoutProxyIntegrationCard;

+ 29 - 3
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -4,14 +4,31 @@ import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxyIntegrationCard from './CustomBotWithoutProxyIntegrationCard';
+import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
 const CustomBotWithoutProxySettings = (props) => {
   const { appContainer } = props;
   const { t } = useTranslation();
 
   const [siteName, setSiteName] = useState('');
+  const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+
+  const deleteSlackSettingsHandler = async() => {
+    try {
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-without-proxy', {
+        slackSigningSecret: '',
+        slackBotToken: '',
+        currentBotType: '',
+      });
+      toastSuccess('success');
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
 
   useEffect(() => {
     const siteName = appContainer.config.crowi.title;
@@ -25,17 +42,28 @@ const CustomBotWithoutProxySettings = (props) => {
       <CustomBotWithoutProxyIntegrationCard
         siteName={siteName}
         slackWSNameInWithoutProxy={props.slackWSNameInWithoutProxy}
-        isSlackScopeSet={props.isSlackScopeSet}
       />
 
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
 
+      <button
+        className="mx-3 pull-right btn text-danger border-danger"
+        type="button"
+        onClick={() => setIsDeleteConfirmModalShown(true)}
+      >{t('admin:slack_integration.reset')}
+      </button>
+
       <div className="my-5 mx-3">
         <CustomBotWithoutProxySettingsAccordion
           {...props}
           activeStep={botInstallationStep.CREATE_BOT}
         />
       </div>
+      <DeleteSlackBotSettingsModal
+        isOpen={isDeleteConfirmModalShown}
+        onClose={() => setIsDeleteConfirmModalShown(false)}
+        onClickDeleteButton={deleteSlackSettingsHandler}
+      />
     </>
   );
 };
@@ -50,8 +78,6 @@ CustomBotWithoutProxySettings.propTypes = {
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   isRgisterSlackCredentials: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
-  isSlackScopeSet: PropTypes.bool,
   slackWSNameInWithoutProxy: PropTypes.string,
 };
 

+ 0 - 1
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -213,7 +213,6 @@ CustomBotWithoutProxySettingsAccordion.propTypes = {
   slackBotTokenEnv: PropTypes.string,
   isRegisterSlackCredentials: PropTypes.bool,
   isSendTestMessage: PropTypes.bool,
-  isConnectedToSlack: PropTypes.bool,
   fetchSlackIntegrationData: PropTypes.func,
   onSetSlackSigningSecret: PropTypes.func,
   onSetSlackBotToken: PropTypes.func,

+ 65 - 0
src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -0,0 +1,65 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+const DeleteSlackBotSettingsModal = React.memo((props) => {
+  const { t } = props;
+
+  function closeModal() {
+    if (props.onClose == null) {
+      return;
+    }
+
+    props.onClose();
+  }
+
+  function deleteSlackCredentialsHandler() {
+    if (props.onClickDeleteButton == null) {
+      return;
+    }
+    props.onClickDeleteButton();
+
+    closeModal();
+  }
+
+  function closeButtonHandler() {
+    closeModal();
+  }
+
+  return (
+    <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
+      <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
+        <span>
+          <i className="icon-fw icon-fire"></i>
+          {t('admin:slack_integration.delete_slackbot_settings')}
+        </span>
+      </ModalHeader>
+      <ModalBody>
+        {t('admin:slack_integration.slackbot_settings_notice')}
+      </ModalBody>
+      <ModalFooter>
+        <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
+        <Button color="danger" onClick={deleteSlackCredentialsHandler}>
+          <i className="icon icon-fire"></i>
+          {t('Reset')}
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+
+});
+
+DeleteSlackBotSettingsModal.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func,
+  onClickDeleteButton: PropTypes.func,
+};
+
+export default withTranslation()(DeleteSlackBotSettingsModal);

+ 16 - 6
src/client/js/components/Admin/SlackIntegration/OfficialbotSettingsAccordion.jsx

@@ -1,13 +1,16 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
-import { withUnstatedContainers } from '../../UnstatedUtils';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
 import Accordion from '../Common/Accordion';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import { toastSuccess } from '../../../util/apiNotification';
+import { withUnstatedContainers } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 
 
 const OfficialBotSettingsAccordion = (props) => {
+  // TODO: apply i18n by GW-5878
   const { t } = useTranslation();
   const { appContainer } = props;
   const growiUrl = appContainer.config.crowi.url;
@@ -67,12 +70,19 @@ const OfficialBotSettingsAccordion = (props) => {
           <div className="d-flex flex-column align-items-center">
             <ol className="p-0">
               <li><p className="ml-2">Slack上で`/growi register`と打つ</p></li>
-              {/* TODO: Copy to clipboard on click by GW5856 */}
               <li>
-                <p className="ml-2"><b>GROWI URL</b>には{growiUrl}
-                  <i className="fa fa-clipboard mx-1 text-secondary" aria-hidden="true"></i>
-                  を貼り付ける
-                </p>
+                <div className="input-group align-items-center ml-2 mb-3">
+                  <b> GROWI URL</b>には
+                  <div className="input-group-prepend mx-1">
+                    <input className="form-control" type="text" value={growiUrl} readOnly />
+                    <CopyToClipboard text={growiUrl} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
+                      <div className="btn input-group-text">
+                        <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
+                      </div>
+                    </CopyToClipboard>
+                  </div>
+                    を貼り付ける
+                </div>
               </li>
               <li><p className="ml-2">上記で発行した<b>Access Token for GROWI と Access Token for Proxy</b>を入れる</p></li>
             </ol>

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

@@ -22,32 +22,26 @@ const SlackIntegration = (props) => {
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const [isConnectedToSlack, setIsConnectedToSlack] = useState(false);
   const [isRegisterSlackCredentials, setIsRegisterSlackCredentials] = useState(false);
   const [isSendTestMessage, setIsSendTestMessage] = useState(false);
   const [slackWSNameInWithoutProxy, setSlackWSNameInWithoutProxy] = useState(null);
-  const [isSlackScopeSet, setIsSlackScopeSet] = useState(false);
 
   const fetchSlackWorkSpaceNameInWithoutProxy = useCallback(async() => {
-    if (!isConnectedToSlack) {
-      return setSlackWSNameInWithoutProxy(null);
-    }
+
     try {
       const res = await appContainer.apiv3.get('/slack-integration-settings/custom-bot-without-proxy/slack-workspace-name');
       setSlackWSNameInWithoutProxy(res.data.slackWorkSpaceName);
-      setIsSlackScopeSet(true);
     }
     catch (err) {
       if (err[0].message === 'missing_scope') {
         setSlackWSNameInWithoutProxy(null);
-        setIsSlackScopeSet(false);
         toastError(err, t('admin:slack_integration.set_scope'));
       }
       else {
         toastError(err);
       }
     }
-  }, [appContainer.apiv3, isConnectedToSlack, t]);
+  }, [appContainer.apiv3, t]);
 
   const fetchSlackIntegrationData = useCallback(async() => {
     try {
@@ -55,7 +49,6 @@ const SlackIntegration = (props) => {
       const { currentBotType, customBotWithoutProxySettings } = response.data.slackBotSettingParams;
       const {
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
-        isConnectedToSlack,
       } = customBotWithoutProxySettings;
 
       setCurrentBotType(currentBotType);
@@ -63,18 +56,7 @@ const SlackIntegration = (props) => {
       setSlackBotToken(slackBotToken);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackBotTokenEnv(slackBotTokenEnvVars);
-      setIsConnectedToSlack(isConnectedToSlack);
-
       fetchSlackWorkSpaceNameInWithoutProxy();
-
-      if (isConnectedToSlack) {
-        setIsRegisterSlackCredentials(true);
-      }
-      else {
-        setIsRegisterSlackCredentials(false);
-        setIsSendTestMessage(false);
-      }
-
     }
     catch (err) {
       toastError(err);
@@ -112,13 +94,10 @@ const SlackIntegration = (props) => {
       setSelectedBotType(null);
       toastSuccess(t('admin:slack_integration.bot_reset_successful'));
       setIsRegisterSlackCredentials(false);
-      setIsConnectedToSlack(false);
       setSlackSigningSecret(null);
       setSlackBotToken(null);
-      setIsConnectedToSlack(false);
       setIsSendTestMessage(false);
       setSlackWSNameInWithoutProxy(null);
-      setIsSlackScopeSet(false);
     }
     catch (err) {
       toastError(err);
@@ -136,8 +115,6 @@ const SlackIntegration = (props) => {
         <CustomBotWithoutProxySettings
           isSendTestMessage={isSendTestMessage}
           isRegisterSlackCredentials={isRegisterSlackCredentials}
-          isConnectedToSlack={isConnectedToSlack}
-          isSlackScopeSet={isSlackScopeSet}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotToken={slackBotToken}
           slackSigningSecretEnv={slackSigningSecretEnv}

+ 1 - 0
src/server/models/index.js

@@ -18,4 +18,5 @@ module.exports = {
   GlobalNotificationMailSetting: require('./GlobalNotificationSetting/GlobalNotificationMailSetting'),
   GlobalNotificationSlackSetting: require('./GlobalNotificationSetting/GlobalNotificationSlackSetting'),
   ShareLink: require('./share-link'),
+  SlackAppIntegration: require('./slack-app-integration'),
 };

+ 10 - 0
src/server/models/slack-app-integration.js

@@ -0,0 +1,10 @@
+module.exports = function(crowi) {
+  const mongoose = require('mongoose');
+
+  const slackAppIntegrationSchema = new mongoose.Schema({
+    tokenGtoP: { type: String, required: true, unique: true },
+    tokenPtoG: { type: String, required: true, unique: true },
+  });
+
+  return mongoose.model('SlackAppIntegration', slackAppIntegrationSchema);
+};

+ 17 - 4
src/server/service/slackbot.js

@@ -3,7 +3,7 @@ const mongoose = require('mongoose');
 
 const PAGINGLIMIT = 10;
 
-const { WebClient, LogLevel } = require('@slack/web-api');
+const { generateWebClient } = require('@growi/slack');
 
 const S2sMessage = require('../models/vo/s2s-message');
 const S2sMessageHandlable = require('./s2s-messaging/handlable');
@@ -28,10 +28,23 @@ class SlackBotService extends S2sMessageHandlable {
 
   async initialize() {
     this.isConnectedToSlack = false;
-    const token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+    const currentBotType = this.crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
 
-    if (token != null) {
-      this.client = new WebClient(token, { logLevel: LogLevel.DEBUG });
+    if (currentBotType != null) {
+      let serverUri;
+      let token;
+
+      // connect to proxy
+      if (currentBotType !== 'customBotWithoutProxy') {
+        // TODO: https://youtrack.weseek.co.jp/issue/GW-5896
+        serverUri = 'http://localhost:8080/slack-api-proxy/';
+      }
+      // connect directly
+      else {
+        token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
+      }
+
+      this.client = generateWebClient(token, serverUri);
       logger.debug('SlackBot: setup is done');
       await this.sendAuthTest();
     }