Parcourir la source

Merge branch 'feat/growi-bot' into imprv/gw6231-add-url-validation-to-register-modal

kaori il y a 4 ans
Parent
commit
d87f365a70

+ 39 - 3
packages/slackbot-proxy/src/controllers/slack.ts

@@ -19,6 +19,7 @@ import { AddSigningSecretToReq } from '~/middlewares/slack-to-growi/add-signing-
 import { AuthorizeCommandMiddleware, AuthorizeInteractionMiddleware } from '~/middlewares/slack-to-growi/authorizer';
 import { AuthorizeCommandMiddleware, AuthorizeInteractionMiddleware } from '~/middlewares/slack-to-growi/authorizer';
 import { InstallerService } from '~/services/InstallerService';
 import { InstallerService } from '~/services/InstallerService';
 import { RegisterService } from '~/services/RegisterService';
 import { RegisterService } from '~/services/RegisterService';
+import { UnregisterService } from '~/services/UnregisterService';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 
@@ -43,6 +44,9 @@ export class SlackCtrl {
   @Inject()
   @Inject()
   registerService: RegisterService;
   registerService: RegisterService;
 
 
+  @Inject()
+  unregisterService: UnregisterService;
+
   @Get('/install')
   @Get('/install')
   async install(): Promise<string> {
   async install(): Promise<string> {
     const url = await this.installerService.installer.generateInstallUrl({
     const url = await this.installerService.installer.generateInstallUrl({
@@ -83,9 +87,22 @@ export class SlackCtrl {
       return this.registerService.process(growiCommand, authorizeResult, body as {[key:string]:string});
       return this.registerService.process(growiCommand, authorizeResult, body as {[key:string]:string});
     }
     }
 
 
-    /*
-     * forward to GROWI server
-     */
+    // unregister
+    if (growiCommand.growiCommandType === 'unregister') {
+      if (growiCommand.growiCommandArgs.length === 0) {
+        return 'GROWI Urls is required.';
+      }
+      if (!growiCommand.growiCommandArgs.every(v => v.match(/^(https?:\/\/)/))) {
+        return 'GROWI Urls must be urls.';
+      }
+
+      // Send response immediately to avoid opelation_timeout error
+      // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
+      res.send();
+
+      return this.unregisterService.process(growiCommand, authorizeResult, body as {[key:string]:string});
+    }
+
     const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
     const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
     const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
@@ -100,10 +117,23 @@ export class SlackCtrl {
       });
       });
     }
     }
 
 
+    // status
+    if (growiCommand.growiCommandType === 'status') {
+      return res.json({
+        blocks: [
+          generateMarkdownSectionBlock('*Found Relations to GROWI.*'),
+          ...relations.map(relation => generateMarkdownSectionBlock(`GROWI url: ${relation.growiUri}.`)),
+        ],
+      });
+    }
+
     // Send response immediately to avoid opelation_timeout error
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();
     res.send();
 
 
+    /*
+     * forward to GROWI server
+     */
     const promises = relations.map((relation: Relation) => {
     const promises = relations.map((relation: Relation) => {
       // generate API URL
       // generate API URL
       const url = new URL('/_api/v3/slack-integration/proxied/commands', relation.growiUri);
       const url = new URL('/_api/v3/slack-integration/proxied/commands', relation.growiUri);
@@ -171,6 +201,12 @@ export class SlackCtrl {
       return;
       return;
     }
     }
 
 
+    // unregister
+    if (callBackId === 'unregister') {
+      await this.unregisterService.unregister(this.relationRepository, installation, authorizeResult, payload);
+      return;
+    }
+
     /*
     /*
      * forward to GROWI server
      * forward to GROWI server
      */
      */

+ 72 - 0
packages/slackbot-proxy/src/services/UnregisterService.ts

@@ -0,0 +1,72 @@
+import { Service } from '@tsed/di';
+import { WebClient, LogLevel } from '@slack/web-api';
+import { GrowiCommand, generateMarkdownSectionBlock } from '@growi/slack';
+import { AuthorizeResult } from '@slack/oauth';
+import { GrowiCommandProcessor } from '~/interfaces/slack-to-growi/growi-command-processor';
+import { RelationRepository } from '~/repositories/relation';
+import { Installation } from '~/entities/installation';
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+@Service()
+export class UnregisterService implements GrowiCommandProcessor {
+
+  async process(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult, body: {[key:string]:string}): Promise<void> {
+    const { botToken } = authorizeResult;
+    const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+    const growiUrls = growiCommand.growiCommandArgs;
+    await client.views.open({
+      trigger_id: body.trigger_id,
+      view: {
+        type: 'modal',
+        callback_id: 'unregister',
+        title: {
+          type: 'plain_text',
+          text: 'Unregister Credentials',
+        },
+        submit: {
+          type: 'plain_text',
+          text: 'Submit',
+        },
+        close: {
+          type: 'plain_text',
+          text: 'Close',
+        },
+        private_metadata: JSON.stringify({ channel: body.channel_name, growiUrls }),
+
+        blocks: [
+          ...growiUrls.map(growiCommandArg => generateMarkdownSectionBlock(`GROWI url: ${growiCommandArg}.`)),
+        ],
+      },
+    });
+  }
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  async unregister(relationRepository:RelationRepository, installation:Installation | undefined, authorizeResult: AuthorizeResult, payload: any):Promise<void> {
+    const { botToken } = authorizeResult;
+    const { channel, growiUrls } = JSON.parse(payload.view.private_metadata);
+    const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+
+    const deleteResult = await relationRepository.createQueryBuilder('relation')
+      .where('relation.growiUri IN (:uris)', { uris: growiUrls })
+      .andWhere('relation.installationId = :installationId', { installationId: installation?.id })
+      .delete()
+      .execute();
+
+    await client.chat.postEphemeral({
+      channel,
+      user: payload.user.id,
+      // Recommended including 'text' to provide a fallback when using blocks
+      // refer to https://api.slack.com/methods/chat.postEphemeral#text_usage
+      text: 'Delete Relations',
+      blocks: [
+        generateMarkdownSectionBlock(`Deleted ${deleteResult.affected} Relations.`),
+      ],
+    });
+
+    return;
+
+  }
+
+
+}

+ 1 - 1
resource/locales/en_US/admin/admin.json

@@ -301,7 +301,7 @@
     "reset": "Reset",
     "reset": "Reset",
     "reset_all_settings": "Reset all settings",
     "reset_all_settings": "Reset all settings",
     "delete_slackbot_settings": "Delete Slack Bot settings",
     "delete_slackbot_settings": "Delete Slack Bot settings",
-    "slackbot_settings_notice": "Delete",
+    "slackbot_settings_notice": "The Slack workspace integration procedure will be deleted. <br> Are you sure?",
     "all_settings_of_the_bot_will_be_reset": "All settings of the Bot will be reset.<br>Are you sure?",
     "all_settings_of_the_bot_will_be_reset": "All settings of the Bot will be reset.<br>Are you sure?",
     "accordion": {
     "accordion": {
       "create_bot": "Create Bot",
       "create_bot": "Create Bot",

+ 1 - 0
resource/locales/en_US/translation.json

@@ -438,6 +438,7 @@
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "give_user_admin": "Succeeded to give {{username}} admin",
     "give_user_admin": "Succeeded to give {{username}} admin",
     "remove_user_admin": "Succeeded to remove {{username}} admin",
     "remove_user_admin": "Succeeded to remove {{username}} admin",
+    "delete_slack_integration_procedure": "Succeeded to delete the slack integration procedure",
     "activate_user_success": "Succeeded to activating {{username}}",
     "activate_user_success": "Succeeded to activating {{username}}",
     "deactivate_user_success": "Succeeded to deactivate {{username}}",
     "deactivate_user_success": "Succeeded to deactivate {{username}}",
     "remove_user_success": "Succeeded to removing {{username}}",
     "remove_user_success": "Succeeded to removing {{username}}",

+ 2 - 2
resource/locales/ja_JP/admin/admin.json

@@ -298,7 +298,7 @@
     "reset": "リセット",
     "reset": "リセット",
     "reset_all_settings": "全ての設定をリセット",
     "reset_all_settings": "全ての設定をリセット",
     "delete_slackbot_settings": "Slack Bot 設定を削除する",
     "delete_slackbot_settings": "Slack Bot 設定を削除する",
-    "slackbot_settings_notice": "削除します",
+    "slackbot_settings_notice": "Slak ワークスペースの連携手順が削除されます。<br>よろしいですか?",
     "all_settings_of_the_bot_will_be_reset": "Botの全ての設定がリセットされます。<br>よろしいですか?",
     "all_settings_of_the_bot_will_be_reset": "Botの全ての設定がリセットされます。<br>よろしいですか?",
     "accordion": {
     "accordion": {
       "create_bot": "Bot を作成する",
       "create_bot": "Bot を作成する",
@@ -331,7 +331,7 @@
     },
     },
     "custom_bot_without_proxy_integration": "Custom bot without proxy 連携",
     "custom_bot_without_proxy_integration": "Custom bot without proxy 連携",
     "integration_sentence": {
     "integration_sentence": {
-      "integration_is_not_complete": "連携は完了していません<br>下の連携手順を進めてください",
+      "integration_is_not_complete": "連携は完了していません<br>下の連携手順を進めてください",
       "integration_successful": "連携は完了しています。",
       "integration_successful": "連携は完了しています。",
       "integration_some_ws_is_not_complete": "連携に失敗している ワークスペースがあります。"
       "integration_some_ws_is_not_complete": "連携に失敗している ワークスペースがあります。"
 
 

+ 1 - 0
resource/locales/ja_JP/translation.json

@@ -440,6 +440,7 @@
     "initialize_successed": "{{target}}を初期化しました",
     "initialize_successed": "{{target}}を初期化しました",
     "give_user_admin": "{{username}}を管理者に設定しました",
     "give_user_admin": "{{username}}を管理者に設定しました",
     "remove_user_admin": "{{username}}を管理者から外しました",
     "remove_user_admin": "{{username}}を管理者から外しました",
+    "delete_slack_integration_procedure": "Slack 連携手順を削除しました",
     "activate_user_success": "{{username}}を有効化しました",
     "activate_user_success": "{{username}}を有効化しました",
     "deactivate_user_success": "{{username}}を無効化しました",
     "deactivate_user_success": "{{username}}を無効化しました",
     "remove_user_success": "{{username}}を削除しました",
     "remove_user_success": "{{username}}を削除しました",

+ 1 - 1
resource/locales/zh_CN/admin/admin.json

@@ -308,7 +308,7 @@
     "reset":"重置",
     "reset":"重置",
     "reset_all_settings": "重置所有设置",
     "reset_all_settings": "重置所有设置",
     "delete_slackbot_settings": "删除 Slack Bot 设置",
     "delete_slackbot_settings": "删除 Slack Bot 设置",
-    "slackbot_settings_notice": "删除",
+    "slackbot_settings_notice": "Slak 工作区集成过程已被删除。 <br> 你确定吗?",
     "all_settings_of_the_bot_will_be_reset": "bot的所有设置将被重置。<br>你确定吗?",
     "all_settings_of_the_bot_will_be_reset": "bot的所有设置将被重置。<br>你确定吗?",
     "accordion": {
     "accordion": {
       "create_bot": "创建 Bot",
       "create_bot": "创建 Bot",

+ 2 - 1
resource/locales/zh_CN/translation.json

@@ -417,7 +417,8 @@
 		"update_successed": "Succeeded to update {{target}}",
 		"update_successed": "Succeeded to update {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",
 		"give_user_admin": "Succeeded to give {{username}} admin",
 		"give_user_admin": "Succeeded to give {{username}} admin",
-		"remove_user_admin": "Succeeded to remove {{username}} admin ",
+    "remove_user_admin": "Succeeded to remove {{username}} admin ",
+    "delete_slack_integration_procedure": "删除了 Slack 集成程序",
 		"activate_user_success": "Succeeded to activating {{username}}",
 		"activate_user_success": "Succeeded to activating {{username}}",
 		"deactivate_user_success": "Succeeded to deactivate {{username}}",
 		"deactivate_user_success": "Succeeded to deactivate {{username}}",
 		"remove_user_success": "Succeeded to removing {{username}} ",
 		"remove_user_success": "Succeeded to removing {{username}} ",

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

@@ -7,7 +7,7 @@ const ProxyCircle = () => (
   <div className="grw-bridge-proxy-circle">
   <div className="grw-bridge-proxy-circle">
     <div className="circle position-absolute bg-primary border-light rounded-circle">
     <div className="circle position-absolute bg-primary border-light rounded-circle">
       <p className="circle-inner text-light font-weight-bold d-none d-lg-inline">Proxy Server</p>
       <p className="circle-inner text-light font-weight-bold d-none d-lg-inline">Proxy Server</p>
-      <p className="circle-inner mt-5 d-block d-lg-none">ProxyServer</p>
+      <p className="circle-inner grw-proxy-server-name d-block d-lg-none">Proxy Server</p>
     </div>
     </div>
   </div>
   </div>
 );
 );

+ 5 - 8
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.jsx

@@ -14,7 +14,7 @@ const CustomBotWithProxyConnectionStatus = (props) => {
     <div className="d-flex justify-content-center my-5 bot-integration">
     <div className="d-flex justify-content-center my-5 bot-integration">
 
 
       <div className="card rounded shadow border-0 w-50 admin-bot-card">
       <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>
+        <h5 className="card-title font-weight-bold mt-3 ml-3">Slack</h5>
         <div className="card-body px-5">
         <div className="card-body px-5">
           {connectionStatusValues.map((connectionStatus, i) => {
           {connectionStatusValues.map((connectionStatus, i) => {
             const workspaceName = connectionStatus.workspaceName || `Settings #${i}`;
             const workspaceName = connectionStatus.workspaceName || `Settings #${i}`;
@@ -36,14 +36,11 @@ const CustomBotWithProxyConnectionStatus = (props) => {
       </div>
       </div>
 
 
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card">
       <div className="card rounded-lg shadow border-0 w-50 admin-bot-card">
-        <div className="row">
-          <h5 className="card-title font-weight-bold mt-3 ml-4 col">GROWI App</h5>
-          <div className="pull-right mt-3 mr-3">
-            <a className="icon-fw fa fa-repeat fa-2x"></a>
-          </div>
-        </div>
+        <h5 className="card-title font-weight-bold mt-3 ml-3">GROWI App
+          <a className="icon-fw fa fa-repeat float-md-right ml-2 mr-sm-3 fa-sm-lg"></a>
+        </h5>
         <div className="card-body text-center">
         <div className="card-body text-center">
-          <div className="mt-5 border p-2 mx-3 bg-primary text-light">
+          <div className="mx-md-3 my-4 my-lg-5 p-2 border bg-primary text-light">
             {siteName}
             {siteName}
           </div>
           </div>
         </div>
         </div>

+ 8 - 11
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -13,7 +13,7 @@ const logger = loggerFactory('growi:SlackBotSettings');
 
 
 const CustomBotWithProxySettings = (props) => {
 const CustomBotWithProxySettings = (props) => {
   const {
   const {
-    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn, connectionStatuses,
+    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn, connectionStatuses, onUpdateTokens,
   } = props;
   } = props;
   const [newProxyServerUri, setNewProxyServerUri] = useState();
   const [newProxyServerUri, setNewProxyServerUri] = useState();
   const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
   const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
@@ -26,12 +26,6 @@ const CustomBotWithProxySettings = (props) => {
     }
     }
   }, [proxyServerUri]);
   }, [proxyServerUri]);
 
 
-  const fetchSlackIntegrationData = () => {
-    if (props.fetchSlackIntegrationData != null) {
-      props.fetchSlackIntegrationData();
-    }
-  };
-
   const addSlackAppIntegrationHandler = async() => {
   const addSlackAppIntegrationHandler = async() => {
     if (onClickAddSlackWorkspaceBtn != null) {
     if (onClickAddSlackWorkspaceBtn != null) {
       onClickAddSlackWorkspaceBtn();
       onClickAddSlackWorkspaceBtn();
@@ -40,10 +34,11 @@ const CustomBotWithProxySettings = (props) => {
 
 
   const deleteSlackAppIntegrationHandler = async() => {
   const deleteSlackAppIntegrationHandler = async() => {
     try {
     try {
-      // GW-6068 set new value after this
       await appContainer.apiv3.delete('/slack-integration-settings/slack-app-integration', { integrationIdToDelete });
       await appContainer.apiv3.delete('/slack-integration-settings/slack-app-integration', { integrationIdToDelete });
-      fetchSlackIntegrationData();
-      toastSuccess(t('toaster.update_successed', { target: 'Token' }));
+      if (props.onDeleteSlackAppIntegration != null) {
+        props.onDeleteSlackAppIntegration();
+      }
+      toastSuccess(t('toaster.delete_slack_integration_procedure'));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -127,6 +122,7 @@ const CustomBotWithProxySettings = (props) => {
                 slackAppIntegrationId={slackAppIntegration._id}
                 slackAppIntegrationId={slackAppIntegration._id}
                 tokenGtoP={tokenGtoP}
                 tokenGtoP={tokenGtoP}
                 tokenPtoG={tokenPtoG}
                 tokenPtoG={tokenPtoG}
+                onUpdateTokens={onUpdateTokens}
               />
               />
             </React.Fragment>
             </React.Fragment>
           );
           );
@@ -162,8 +158,9 @@ CustomBotWithProxySettings.propTypes = {
   slackAppIntegrations: PropTypes.array,
   slackAppIntegrations: PropTypes.array,
   proxyServerUri: PropTypes.string,
   proxyServerUri: PropTypes.string,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
-  fetchSlackIntegrationData: PropTypes.func,
+  onDeleteSlackAppIntegration: PropTypes.func,
   connectionStatuses: PropTypes.object.isRequired,
   connectionStatuses: PropTypes.object.isRequired,
+  onUpdateTokens: PropTypes.func,
 };
 };
 
 
 export default CustomBotWithProxySettingsWrapper;
 export default CustomBotWithProxySettingsWrapper;

+ 10 - 37
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -2,39 +2,14 @@ import React, { useState, useEffect } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
-import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
 import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
 
 
 const CustomBotWithoutProxySettings = (props) => {
 const CustomBotWithoutProxySettings = (props) => {
-  const { appContainer, connectionStatuses, onTestConnectionInvoked } = props;
+  const { appContainer, connectionStatuses } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
-
   const [siteName, setSiteName] = useState('');
   const [siteName, setSiteName] = useState('');
-  const [isIntegrationSuccess, setIsIntegrationSuccess] = useState(false);
-  const [connectionMessage, setConnectionMessage] = useState(null);
-  const [testChannel, setTestChannel] = useState('');
-
-  const testConnection = async() => {
-    try {
-      await appContainer.apiv3.post('/slack-integration-settings/without-proxy/test', { channel: testChannel });
-      setConnectionMessage('');
-      setIsIntegrationSuccess(true);
-
-      if (onTestConnectionInvoked != null) {
-        onTestConnectionInvoked();
-      }
-    }
-    catch (err) {
-      setConnectionMessage(err[0]);
-      setIsIntegrationSuccess(false);
-    }
-  };
-
-  const inputTestChannelHandler = (channel) => {
-    setTestChannel(channel);
-  };
 
 
   useEffect(() => {
   useEffect(() => {
     const siteName = appContainer.config.crowi.title;
     const siteName = appContainer.config.crowi.title;
@@ -44,7 +19,6 @@ const CustomBotWithoutProxySettings = (props) => {
   const workspaceName = connectionStatuses[props.slackBotToken]?.workspaceName;
   const workspaceName = connectionStatuses[props.slackBotToken]?.workspaceName;
 
 
   return (
   return (
-
     <>
     <>
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_integration')}
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_integration')}
         {/* TODO: add an appropriate links by GW-5614 */}
         {/* TODO: add an appropriate links by GW-5614 */}
@@ -65,33 +39,32 @@ const CustomBotWithoutProxySettings = (props) => {
           </h2>
           </h2>
         </div>
         </div>
         <CustomBotWithoutProxySettingsAccordion
         <CustomBotWithoutProxySettingsAccordion
-          {...props}
           activeStep={botInstallationStep.CREATE_BOT}
           activeStep={botInstallationStep.CREATE_BOT}
-          connectionMessage={connectionMessage}
-          isIntegrationSuccess={isIntegrationSuccess}
-          testChannel={testChannel}
-          onTestFormSubmitted={testConnection}
-          inputTestChannelHandler={inputTestChannelHandler}
+          slackBotTokenEnv={props.slackBotTokenEnv}
+          slackBotToken={props.slackBotToken}
+          slackSigningSecretEnv={props.slackSigningSecretEnv}
+          slackSigningSecret={props.slackSigningSecret}
+          onTestConnectionInvoked={props.onTestConnectionInvoked}
+          onUpdatedSecretToken={props.onUpdatedSecretToken}
         />
         />
       </div>
       </div>
     </>
     </>
   );
   );
 };
 };
 
 
-const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer, AdminAppContainer]);
+const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer]);
 
 
 CustomBotWithoutProxySettings.propTypes = {
 CustomBotWithoutProxySettings.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
 
 
   slackSigningSecret: PropTypes.string,
   slackSigningSecret: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
 
 
-  isIntegrationSuccess: PropTypes.bool,
+  onUpdatedSecretToken: PropTypes.func.isRequired,
+  onTestConnectionInvoked: PropTypes.func.isRequired,
   connectionStatuses: PropTypes.object.isRequired,
   connectionStatuses: PropTypes.object.isRequired,
-  onTestConnectionInvoked: PropTypes.func,
 };
 };
 
 
 export default CustomBotWithoutProxySettingsWrapper;
 export default CustomBotWithoutProxySettingsWrapper;

+ 63 - 36
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -2,7 +2,11 @@ import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import Accordion from '../Common/Accordion';
 import Accordion from '../Common/Accordion';
+import AppContainer from '../../../services/AppContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomBotWithoutProxySecretTokenSection from './CustomBotWithoutProxySecretTokenSection';
 import CustomBotWithoutProxySecretTokenSection from './CustomBotWithoutProxySecretTokenSection';
+import { addLogs } from './slak-integration-util';
+
 
 
 export const botInstallationStep = {
 export const botInstallationStep = {
   CREATE_BOT: 'create-bot',
   CREATE_BOT: 'create-bot',
@@ -11,35 +15,66 @@ export const botInstallationStep = {
   CONNECTION_TEST: 'connection-test',
   CONNECTION_TEST: 'connection-test',
 };
 };
 
 
+const MessageBasedOnConnection = (props) => {
+  const { isLatestConnectionSuccess, logsValue } = props;
+  const { t } = useTranslation();
+  if (isLatestConnectionSuccess) {
+    return <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>;
+  }
+
+  if (logsValue === '') {
+    return <p></p>;
+  }
+
+  return <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>;
+};
+
+MessageBasedOnConnection.propTypes = {
+  isLatestConnectionSuccess: PropTypes.bool.isRequired,
+  logsValue: PropTypes.string.isRequired,
+};
+
+
 const CustomBotWithoutProxySettingsAccordion = (props) => {
 const CustomBotWithoutProxySettingsAccordion = (props) => {
   const {
   const {
-    activeStep, connectionMessage, testChannel,
+    appContainer, activeStep, onTestConnectionInvoked,
     slackSigningSecret, slackBotToken, slackSigningSecretEnv, slackBotTokenEnv,
     slackSigningSecret, slackBotToken, slackSigningSecretEnv, slackBotTokenEnv,
-    isIntegrationSuccess,
-    inputTestChannelHandler, onTestFormSubmitted,
   } = props;
   } = props;
+  const successMessage = 'Successfully sent to Slack workspace.';
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
-  // TODO: GW-5644 Store default open accordion
   // eslint-disable-next-line no-unused-vars
   // eslint-disable-next-line no-unused-vars
   const [defaultOpenAccordionKeys, setDefaultOpenAccordionKeys] = useState(new Set([activeStep]));
   const [defaultOpenAccordionKeys, setDefaultOpenAccordionKeys] = useState(new Set([activeStep]));
+  const [isLatestConnectionSuccess, setIsLatestConnectionSuccess] = useState(false);
+  const [testChannel, setTestChannel] = useState('');
+  const [logsValue, setLogsValue] = useState('');
+
+  const testConnection = async() => {
+    try {
+      await appContainer.apiv3.post('/slack-integration-settings/without-proxy/test', { channel: testChannel });
+      setIsLatestConnectionSuccess(true);
+      if (onTestConnectionInvoked != null) {
+        onTestConnectionInvoked();
+        const newLogs = addLogs(logsValue, successMessage, null);
+        setLogsValue(newLogs);
+      }
+    }
+    catch (err) {
+      setIsLatestConnectionSuccess(false);
+      const newLogs = addLogs(logsValue, err[0].message, err[0].code);
+      setLogsValue(newLogs);
+    }
+  };
+
+  const inputTestChannelHandler = (channel) => {
+    setTestChannel(channel);
+  };
 
 
   const submitForm = (e) => {
   const submitForm = (e) => {
     e.preventDefault();
     e.preventDefault();
-
-    if (onTestFormSubmitted == null) {
-      return;
-    }
-    onTestFormSubmitted();
+    testConnection();
   };
   };
 
 
-  let value = '';
-  if (connectionMessage === '' || connectionMessage == null) {
-    value = '';
-  }
-  else {
-    value = [connectionMessage.code, connectionMessage.message];
-  }
 
 
   const slackSigningSecretCombined = slackSigningSecret || slackSigningSecretEnv;
   const slackSigningSecretCombined = slackSigningSecret || slackSigningSecretEnv;
   const slackBotTokenCombined = slackBotToken || slackBotTokenEnv;
   const slackBotTokenCombined = slackBotToken || slackBotTokenEnv;
@@ -104,7 +139,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         // eslint-disable-next-line max-len
         // eslint-disable-next-line max-len
-        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}{isIntegrationSuccess && <i className="ml-3 text-success fa fa-check"></i>}</>}
+        title={<><span className="mr-2">④</span>{t('admin:slack_integration.accordion.test_connection')}{isLatestConnectionSuccess && <i className="ml-3 text-success fa fa-check"></i>}</>}
       >
       >
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <div className="d-flex justify-content-center">
         <div className="d-flex justify-content-center">
@@ -129,17 +164,9 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
             </button>
             </button>
           </form>
           </form>
         </div>
         </div>
-        {connectionMessage == null
-          ? <p></p>
-          : (
-            <>
-              {connectionMessage === ''
-                ? <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>
-                : <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>
-              }
-            </>
-          )
-        }
+
+        <MessageBasedOnConnection isLatestConnectionSuccess={isLatestConnectionSuccess} logsValue={logsValue} />
+
         <form>
         <form>
           <div className="row my-3 justify-content-center">
           <div className="row my-3 justify-content-center">
             <div className="form-group slack-connection-log col-md-4">
             <div className="form-group slack-connection-log col-md-4">
@@ -147,7 +174,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
               <textarea
               <textarea
                 className="form-control card border-info slack-connection-log-body rounded-lg"
                 className="form-control card border-info slack-connection-log-body rounded-lg"
                 rows="5"
                 rows="5"
-                value={value}
+                value={logsValue}
                 readOnly
                 readOnly
               />
               />
             </div>
             </div>
@@ -159,21 +186,21 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
 };
 };
 
 
 
 
+const CustomBotWithoutProxySettingsAccordionWrapper = withUnstatedContainers(CustomBotWithoutProxySettingsAccordion, [AppContainer]);
+
+
 CustomBotWithoutProxySettingsAccordion.propTypes = {
 CustomBotWithoutProxySettingsAccordion.propTypes = {
   activeStep: PropTypes.oneOf(Object.values(botInstallationStep)).isRequired,
   activeStep: PropTypes.oneOf(Object.values(botInstallationStep)).isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
 
   onUpdatedSecretToken: PropTypes.func,
   onUpdatedSecretToken: PropTypes.func,
+  onTestConnectionInvoked: PropTypes.func,
+
   slackSigningSecret: PropTypes.string,
   slackSigningSecret: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
 
 
-  connectionMessage: PropTypes.string,
-  connectionErrorCode: PropTypes.string,
-  testChannel: PropTypes.string,
-  isIntegrationSuccess: PropTypes.bool,
-  inputTestChannelHandler: PropTypes.func,
-  onTestFormSubmitted: PropTypes.func,
 };
 };
 
 
-export default CustomBotWithoutProxySettingsAccordion;
+export default CustomBotWithoutProxySettingsAccordionWrapper;

+ 8 - 9
src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -51,17 +51,16 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
         {props.isResetAll && (
         {props.isResetAll && (
-          <>
-            <span
-              // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.all_settings_of_the_bot_will_be_reset') }}
-            />
-          </>
+          <span
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.all_settings_of_the_bot_will_be_reset') }}
+          />
         )}
         )}
         {!props.isResetAll && (
         {!props.isResetAll && (
-          <>
-            {t('admin:slack_integration.slackbot_settings_notice')}
-          </>
+          <span
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.slackbot_settings_notice') }}
+          />
         )}
         )}
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>

+ 14 - 22
src/client/js/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -13,10 +13,10 @@ const logger = loggerFactory('growi:SlackBotSettings');
 
 
 const OfficialBotSettings = (props) => {
 const OfficialBotSettings = (props) => {
   const {
   const {
-    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn, connectionStatuses,
+    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn, connectionStatuses, onUpdateTokens,
   } = props;
   } = props;
   const [siteName, setSiteName] = useState('');
   const [siteName, setSiteName] = useState('');
-  const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+  const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const [newProxyServerUri, setNewProxyServerUri] = useState();
   const [newProxyServerUri, setNewProxyServerUri] = useState();
@@ -33,27 +33,17 @@ const OfficialBotSettings = (props) => {
     }
     }
   };
   };
 
 
-  /* commented out to ignore lint error -- 2021.05.31 Yuki Takei
-  const discardTokenHandler = async(tokenGtoP, tokenPtoG) => {
-    try {
-      // GW-6068 set new value after this
-      await appContainer.apiv3.delete('/slack-integration-settings/slack-app-integration', { tokenGtoP, tokenPtoG });
-    }
-    catch (err) {
-      toastError(err);
-      logger(err);
-    }
-  };
-  */
-
   const deleteSlackAppIntegrationHandler = async() => {
   const deleteSlackAppIntegrationHandler = async() => {
+    await appContainer.apiv3.delete('/slack-integration-settings/slack-app-integration', { integrationIdToDelete });
     try {
     try {
-      // TODO GW-5923 delete SlackAppIntegration
-      // await appContainer.apiv3.put('/slack-integration-settings/custom-bot-with-proxy');
-      toastSuccess('success');
+      if (props.onDeleteSlackAppIntegration != null) {
+        props.onDeleteSlackAppIntegration();
+      }
+      toastSuccess(t('toaster.delete_slack_integration_procedure'));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
+      logger.error(err);
     }
     }
   };
   };
 
 
@@ -117,7 +107,7 @@ const OfficialBotSettings = (props) => {
                 <button
                 <button
                   className="btn btn-outline-danger"
                   className="btn btn-outline-danger"
                   type="button"
                   type="button"
-                  onClick={() => setIsDeleteConfirmModalShown(true)}
+                  onClick={() => setIntegrationIdToDelete(slackAppIntegration._id)}
                 >
                 >
                   <i className="icon-trash mr-1" />
                   <i className="icon-trash mr-1" />
                   {t('admin:slack_integration.delete')}
                   {t('admin:slack_integration.delete')}
@@ -128,6 +118,7 @@ const OfficialBotSettings = (props) => {
                 slackAppIntegrationId={slackAppIntegration._id}
                 slackAppIntegrationId={slackAppIntegration._id}
                 tokenGtoP={tokenGtoP}
                 tokenGtoP={tokenGtoP}
                 tokenPtoG={tokenPtoG}
                 tokenPtoG={tokenPtoG}
+                onUpdateTokens={onUpdateTokens}
               />
               />
             </React.Fragment>
             </React.Fragment>
           );
           );
@@ -144,8 +135,8 @@ const OfficialBotSettings = (props) => {
       </div>
       </div>
       <DeleteSlackBotSettingsModal
       <DeleteSlackBotSettingsModal
         isResetAll={false}
         isResetAll={false}
-        isOpen={isDeleteConfirmModalShown}
-        onClose={() => setIsDeleteConfirmModalShown(false)}
+        isOpen={integrationIdToDelete != null}
+        onClose={() => setIntegrationIdToDelete(null)}
         onClickDeleteButton={deleteSlackAppIntegrationHandler}
         onClickDeleteButton={deleteSlackAppIntegrationHandler}
       />
       />
     </>
     </>
@@ -165,8 +156,9 @@ OfficialBotSettings.propTypes = {
   slackAppIntegrations: PropTypes.array,
   slackAppIntegrations: PropTypes.array,
   proxyServerUri: PropTypes.string,
   proxyServerUri: PropTypes.string,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
+  onDeleteSlackAppIntegration: PropTypes.func,
   connectionStatuses: PropTypes.object.isRequired,
   connectionStatuses: PropTypes.object.isRequired,
-
+  onUpdateTokens: PropTypes.func,
 };
 };
 
 
 export default OfficialBotSettingsWrapper;
 export default OfficialBotSettingsWrapper;

+ 4 - 4
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -23,7 +23,6 @@ const SlackIntegration = (props) => {
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const [isRegisterSlackCredentials, setIsRegisterSlackCredentials] = useState(false);
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
   const [slackAppIntegrations, setSlackAppIntegrations] = useState();
   const [slackAppIntegrations, setSlackAppIntegrations] = useState();
   const [proxyServerUri, setProxyServerUri] = useState();
   const [proxyServerUri, setProxyServerUri] = useState();
@@ -89,7 +88,6 @@ const SlackIntegration = (props) => {
       });
       });
       setCurrentBotType(res.data.slackBotTypeParam.slackBotType);
       setCurrentBotType(res.data.slackBotTypeParam.slackBotType);
       setSelectedBotType(null);
       setSelectedBotType(null);
-      setIsRegisterSlackCredentials(false);
       setSlackSigningSecret(null);
       setSlackSigningSecret(null);
       setSlackBotToken(null);
       setSlackBotToken(null);
       setConnectionStatuses({});
       setConnectionStatuses({});
@@ -127,14 +125,15 @@ const SlackIntegration = (props) => {
           slackAppIntegrations={slackAppIntegrations}
           slackAppIntegrations={slackAppIntegrations}
           proxyServerUri={proxyServerUri}
           proxyServerUri={proxyServerUri}
           onClickAddSlackWorkspaceBtn={createSlackIntegrationData}
           onClickAddSlackWorkspaceBtn={createSlackIntegrationData}
+          onDeleteSlackAppIntegration={fetchSlackIntegrationData}
           connectionStatuses={connectionStatuses}
           connectionStatuses={connectionStatuses}
+          onUpdateTokens={fetchSlackIntegrationData}
         />
         />
       );
       );
       break;
       break;
     case 'customBotWithoutProxy':
     case 'customBotWithoutProxy':
       settingsComponent = (
       settingsComponent = (
         <CustomBotWithoutProxySettings
         <CustomBotWithoutProxySettings
-          isRegisterSlackCredentials={isRegisterSlackCredentials}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotTokenEnv={slackBotTokenEnv}
           slackBotToken={slackBotToken}
           slackBotToken={slackBotToken}
           slackSigningSecretEnv={slackSigningSecretEnv}
           slackSigningSecretEnv={slackSigningSecretEnv}
@@ -151,8 +150,9 @@ const SlackIntegration = (props) => {
           slackAppIntegrations={slackAppIntegrations}
           slackAppIntegrations={slackAppIntegrations}
           proxyServerUri={proxyServerUri}
           proxyServerUri={proxyServerUri}
           onClickAddSlackWorkspaceBtn={createSlackIntegrationData}
           onClickAddSlackWorkspaceBtn={createSlackIntegrationData}
-          fetchSlackIntegrationData={fetchSlackIntegrationData}
+          onDeleteSlackAppIntegration={fetchSlackIntegrationData}
           connectionStatuses={connectionStatuses}
           connectionStatuses={connectionStatuses}
+          onUpdateTokens={fetchSlackIntegrationData}
         />
         />
       );
       );
       break;
       break;

+ 28 - 13
src/client/js/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -81,7 +81,9 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers
   const regenerateTokensHandler = async() => {
   const regenerateTokensHandler = async() => {
     try {
     try {
       await appContainer.apiv3.put('/slack-integration-settings/regenerate-tokens', { slackAppIntegrationId });
       await appContainer.apiv3.put('/slack-integration-settings/regenerate-tokens', { slackAppIntegrationId });
-      // TODO: fetch data by GW-6160
+      if (props.onUpdateTokens != null) {
+        props.onUpdateTokens();
+      }
       toastSuccess(t('toaster.update_successed', { target: 'Token' }));
       toastSuccess(t('toaster.update_successed', { target: 'Token' }));
     }
     }
     catch (err) {
     catch (err) {
@@ -177,22 +179,23 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers
 const TestProcess = ({ apiv3Post, slackAppIntegrationId }) => {
 const TestProcess = ({ apiv3Post, slackAppIntegrationId }) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const [testChannel, setTestChannel] = useState('');
   const [testChannel, setTestChannel] = useState('');
-  const [connectionError, setConnectionError] = useState(null);
+  const [latestConnectionMessage, setLatestConnectionMessage] = useState(null);
+  const [isLatestConnectionSuccess, setIsLatestConnectionSuccess] = useState(false);
 
 
-  let value = '';
-  if (connectionError != null) {
-    value = [connectionError.code, connectionError.message];
+  let logsValue = null;
+  if (latestConnectionMessage != null) {
+    logsValue = [latestConnectionMessage.code, latestConnectionMessage.message];
   }
   }
 
 
   const submitForm = async(e) => {
   const submitForm = async(e) => {
     e.preventDefault();
     e.preventDefault();
-    setConnectionError(null);
-
     try {
     try {
       await apiv3Post('/slack-integration-settings/with-proxy/relation-test', { slackAppIntegrationId, channel: testChannel });
       await apiv3Post('/slack-integration-settings/with-proxy/relation-test', { slackAppIntegrationId, channel: testChannel });
+      setIsLatestConnectionSuccess(true);
     }
     }
     catch (error) {
     catch (error) {
-      setConnectionError(error[0]);
+      setIsLatestConnectionSuccess(false);
+      setLatestConnectionMessage(error[0]);
       logger.error(error);
       logger.error(error);
     }
     }
   };
   };
@@ -223,10 +226,7 @@ const TestProcess = ({ apiv3Post, slackAppIntegrationId }) => {
           </button>
           </button>
         </form>
         </form>
       </div>
       </div>
-      {connectionError == null
-        ? <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>
-        : <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>
-      }
+      <MessageBasedOnConnection isLatestConnectionSuccess={isLatestConnectionSuccess} latestConnectionMessage={latestConnectionMessage} />
       <form>
       <form>
         <div className="row my-3 justify-content-center">
         <div className="row my-3 justify-content-center">
           <div className="form-group slack-connection-log col-md-4">
           <div className="form-group slack-connection-log col-md-4">
@@ -234,7 +234,7 @@ const TestProcess = ({ apiv3Post, slackAppIntegrationId }) => {
             <textarea
             <textarea
               className="form-control card border-info slack-connection-log-body rounded-lg"
               className="form-control card border-info slack-connection-log-body rounded-lg"
               rows="5"
               rows="5"
-              value={value}
+              value={logsValue}
               readOnly
               readOnly
             />
             />
           </div>
           </div>
@@ -244,6 +244,19 @@ const TestProcess = ({ apiv3Post, slackAppIntegrationId }) => {
   );
   );
 };
 };
 
 
+const MessageBasedOnConnection = (props) => {
+  const { isLatestConnectionSuccess, latestConnectionMessage } = props;
+  const { t } = useTranslation();
+  if (isLatestConnectionSuccess) {
+    return <p className="text-info text-center my-4">{t('admin:slack_integration.accordion.send_message_to_slack_work_space')}</p>;
+  }
+
+  if (latestConnectionMessage == null) {
+    return <p></p>;
+  }
+
+  return <p className="text-danger text-center my-4">{t('admin:slack_integration.accordion.error_check_logs_below')}</p>;
+};
 
 
 const WithProxyAccordions = (props) => {
 const WithProxyAccordions = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -260,6 +273,7 @@ const WithProxyAccordions = (props) => {
         slackAppIntegrationId={props.slackAppIntegrationId}
         slackAppIntegrationId={props.slackAppIntegrationId}
         tokenPtoG={props.tokenPtoG}
         tokenPtoG={props.tokenPtoG}
         tokenGtoP={props.tokenGtoP}
         tokenGtoP={props.tokenGtoP}
+        onUpdateTokens={props.onUpdateTokens}
       />,
       />,
     },
     },
     '③': {
     '③': {
@@ -288,6 +302,7 @@ const WithProxyAccordions = (props) => {
         slackAppIntegrationId={props.slackAppIntegrationId}
         slackAppIntegrationId={props.slackAppIntegrationId}
         tokenPtoG={props.tokenPtoG}
         tokenPtoG={props.tokenPtoG}
         tokenGtoP={props.tokenGtoP}
         tokenGtoP={props.tokenGtoP}
+        onUpdateTokens={props.onUpdateTokens}
       />,
       />,
     },
     },
     '④': {
     '④': {

+ 20 - 0
src/client/js/components/Admin/SlackIntegration/slak-integration-util.js

@@ -0,0 +1,20 @@
+const addLogs = (log, newLogMessage, newLogCode = undefined) => {
+
+  let newLog;
+  if (newLogCode == null) {
+    newLog = `${new Date()} - ${newLogMessage}\n\n`;
+  }
+  else {
+    newLog = `${new Date()} - ${newLogCode}, ${newLogMessage}\n\n`;
+  }
+
+  if (log == null) {
+    return newLog;
+  }
+  return `${newLog}${log}`;
+};
+
+export {
+  // eslint-disable-next-line import/prefer-default-export
+  addLogs,
+};

+ 7 - 1
src/client/styles/scss/_admin.scss

@@ -155,6 +155,9 @@ $slack-work-space-name-card-border: #efc1f6;
         left: 50%;
         left: 50%;
         transform: translate(-50%, -50%);
         transform: translate(-50%, -50%);
       }
       }
+      .circle-inner.grw-proxy-server-name {
+        margin-top: 55px;
+      }
     }
     }
 
 
     // switch layout for Bridge component
     // switch layout for Bridge component
@@ -169,7 +172,10 @@ $slack-work-space-name-card-border: #efc1f6;
           @extend .mt-0;
           @extend .mt-0;
         }
         }
         .hr-container {
         .hr-container {
-          margin-top: 65px;
+          margin-top: 40px;
+          @include media-breakpoint-up(lg) {
+            margin-top: 65px;
+          }
         }
         }
       }
       }
     }
     }

+ 1 - 0
src/server/routes/apiv3/slack-integration-settings.js

@@ -533,6 +533,7 @@ module.exports = (crowi) => {
     catch (error) {
     catch (error) {
       return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
       return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
     }
     }
+    return res.apiv3();
 
 
   });
   });