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

Merge branch 'feat/growi-bot' into imprv/gw6068-updating-tokens

# Conflicts:
#	src/client/js/components/Admin/SlackIntegration/OfficialBotSettings.jsx
kaori 4 лет назад
Родитель
Сommit
6b25a8b57a

+ 15 - 4
.github/workflows/release-slackbot-proxy.yml

@@ -33,12 +33,22 @@ jobs:
         username: wsmoogle
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
+    - name: Setup gcloud
+      uses: google-github-actions/setup-gcloud@master
+      with:
+        project_id: ${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}
+        service_account_key: ${{ secrets.GCP_SA_KEY_SLACKBOT_PROXY }}
+        export_default_credentials: true
+
+    - name: Configure docker for gcloud
+      run: |
+        gcloud auth configure-docker --quiet
+
     - name: Build and push
       uses: docker/build-push-action@v2
-      working-directory: ./packages/slackbot-proxy
       with:
         context: .
-        file: ./docker/Dockerfile
+        file: ./packages/slackbot-proxy/docker/Dockerfile
         platforms: linux/amd64
         push: true
         tags: |
@@ -46,12 +56,13 @@ jobs:
           weseek/growi-slackbot-proxy:${{ env.RELEASE_VERSION }}
           ghcr.io/weseek/growi-slackbot-proxy:latest
           ghcr.io/weseek/growi-slackbot-proxy:${{ env.RELEASE_VERSION }}
+          asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy:latest
+          asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy:${{ env.RELEASE_VERSION }}
 
     - name: Update Docker Hub Description
       uses: peter-evans/dockerhub-description@v2
-      working-directory: ./packages/slackbot-proxy
       with:
         username: wsmoogle
         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
         repository: weseek/growi-slackbot-proxy
-        readme-filepath: ./docker/README.md
+        readme-filepath: ./packages/slackbot-proxy/docker/README.md

+ 1 - 0
packages/slackbot-proxy/docker/Dockerfile

@@ -65,6 +65,7 @@ RUN tar cf packages.tar \
   packages/slack/package.json \
   packages/slack/dist \
   packages/slackbot-proxy/package.json \
+  packages/slackbot-proxy/.env \
   packages/slackbot-proxy/dist
 
 

+ 1 - 1
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "0.9.0-RC",
+  "version": "0.9.1-RC",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 7 - 8
packages/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -1,5 +1,5 @@
 import {
-  Controller, Get, Post, Inject, Req, Res, UseBefore,
+  Controller, Get, Post, Inject, Req, Res, UseBefore, PathParams,
 } from '@tsed/common';
 import axios from 'axios';
 
@@ -163,9 +163,9 @@ export class GrowiToSlackCtrl {
     return res.send({ relation: createdRelation, slackBotToken: token });
   }
 
-  @Post('/*')
+  @Post('/:method')
   @UseBefore(verifyGrowiToSlackRequest)
-  async postResult(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  async postResult(@PathParams('method') method: string, @Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
     const { tokenGtoPs } = req;
 
     if (tokenGtoPs.length !== 1) {
@@ -192,14 +192,13 @@ export class GrowiToSlackCtrl {
     const client = generateWebClient(token);
 
     try {
-      // TODO: GW-6133
       const opt = req.body as WebAPICallOptions;
-      await client.apiCall('put', opt);
+      opt.headers = req.headers;
+      await client.apiCall(method, opt);
     }
     catch (err) {
-      // TODO: GW-6133
-      // logger.error()
-      return res.status(500).send({ message: err.message });
+      logger.error(err);
+      return res.status(400).send({ message: `failed to send to slack. err: ${err.message}` });
     }
 
     logger.debug('postMessage is success');

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

@@ -263,7 +263,6 @@
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "Detailed explanation",
-      "selecting_bot_type": "・Select bot type",
       "official_bot": "Official bot",
       "custom_bot": "Custom bot",
       "without_proxy": "without proxy",
@@ -301,8 +300,8 @@
     "official_bot_settings": "Official bot Settings",
     "reset": "Reset",
     "reset_all_settings": "Reset all settings",
-    "delete_slackbot_settings": "Reset Slack Bot settings",
-    "slackbot_settings_notice": "Reset",
+    "delete_slackbot_settings": "Delete Slack Bot settings",
+    "slackbot_settings_notice": "Delete",
     "all_settings_of_the_bot_will_be_reset": "All settings of the Bot will be reset.<br>Are you sure?",
     "accordion": {
       "create_bot": "Create Bot",
@@ -336,7 +335,8 @@
     "custom_bot_without_proxy_integration": "Custom Bot Without Proxy Integration",
     "integration_sentence": {
       "integration_is_not_complete": "Integration is not complete.<br>Proceed with the following integration procedure.",
-      "integration_successful": "Integration successful"
+      "integration_successful": "Integration successful",
+      "integration_some_ws_is_not_complete": "Some work spaces is not linked"
     },
     "custom_bot_with_proxy_integration": "Custom Bot With Proxy Integration",
     "official_bot_integration": "Official bot integration"

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

@@ -261,7 +261,6 @@
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "詳しい説明はこちら",
-      "selecting_bot_type": "・Botタイプを選択する",
       "official_bot": "Official bot",
       "custom_bot": "Custom bot",
       "without_proxy": "without proxy",
@@ -298,8 +297,8 @@
     "integration_failed":"連携に失敗しました",
     "reset": "リセット",
     "reset_all_settings": "全ての設定をリセット",
-    "delete_slackbot_settings": "Slack Bot 設定をリセットする",
-    "slackbot_settings_notice": "リセットします",
+    "delete_slackbot_settings": "Slack Bot 設定を削除する",
+    "slackbot_settings_notice": "削除します",
     "all_settings_of_the_bot_will_be_reset": "Botの全ての設定がリセットされます。<br>よろしいですか?",
     "accordion": {
       "create_bot": "Bot を作成する",
@@ -333,7 +332,9 @@
     "custom_bot_without_proxy_integration": "Custom bot without proxy 連携",
     "integration_sentence": {
       "integration_is_not_complete": "連携は完了していません。<br>下記の連携手順を進めてください。",
-      "integration_successful": "連携は完了しています。"
+      "integration_successful": "連携は完了しています。",
+      "integration_some_ws_is_not_complete": "連携に失敗している ワークスペースがあります。"
+
     },
     "custom_bot_with_proxy_integration": "Custom bot with proxy 連携",
     "official_bot_integration": "Official bot 連携"

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

@@ -271,7 +271,6 @@
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "详细说明",
-      "selecting_bot_type": "・选择机器人类型",
       "official_bot": "Official bot",
       "custom_bot": "Custom bot",
       "without_proxy": "without proxy",
@@ -308,8 +307,8 @@
     "integration_failed":"联动失败",
     "reset":"重置",
     "reset_all_settings": "重置所有设置",
-    "delete_slackbot_settings": "重置 Slack Bot 设置",
-    "slackbot_settings_notice": "重置",
+    "delete_slackbot_settings": "删除 Slack Bot 设置",
+    "slackbot_settings_notice": "删除",
     "all_settings_of_the_bot_will_be_reset": "bot的所有设置将被重置。<br>你确定吗?",
     "accordion": {
       "create_bot": "创建 Bot",
@@ -343,7 +342,8 @@
     "custom_bot_without_proxy_integration": "Custom bot without proxy 一体化",
     "integration_sentence": {
       "integration_is_not_complete": "一体化未完成。<br>进行以下一体化程序。",
-      "integration_successful": "一体化成功"
+      "integration_successful": "一体化成功",
+      "integration_some_ws_is_not_complete": "有的工作空间未能连接"
     },
     "custom_bot_with_proxy_integration": "Custom bot with proxy 一体化",
     "official_bot_integration": "Official bot 一体化"

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

@@ -0,0 +1,94 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+import { UncontrolledTooltip } from 'reactstrap';
+
+const ProxyCircle = () => (
+  <div className="grw-bridge-proxy-circle position-absolute bg-primary border-light">
+    <p className="circle-inner text-light font-weight-bold">Proxy Server</p>
+  </div>
+);
+
+const BridgeCore = (props) => {
+  const {
+    description, iconClass, hrClass, withProxy,
+  } = props;
+
+  return (
+    <>
+      <div id="grw-bridge-container" className={`grw-bridge-container ${withProxy ? 'with-proxy' : ''}`}>
+        <p className="label">
+          <i className={iconClass} />
+          <small
+            className="ml-2 d-none d-lg-inline"
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{ __html: description }}
+          />
+        </p>
+        <div className="hr-container">
+          { withProxy && <ProxyCircle /> }
+          <hr className={`align-self-center ${hrClass}`} />
+        </div>
+      </div>
+      <UncontrolledTooltip placement="top" fade={false} target="grw-bridge-container" className="d-block d-lg-none">
+        <small
+          // eslint-disable-next-line react/no-danger
+          dangerouslySetInnerHTML={{ __html: description }}
+        />
+      </UncontrolledTooltip>
+    </>
+  );
+};
+
+BridgeCore.propTypes = {
+  description: PropTypes.string.isRequired,
+  iconClass: PropTypes.string.isRequired,
+  hrClass: PropTypes.string.isRequired,
+  withProxy: PropTypes.bool,
+};
+
+
+const Bridge = (props) => {
+  const { t } = useTranslation();
+  const { errorCount, totalCount, withProxy } = props;
+
+  let description;
+  let iconClass;
+  let hrClass;
+
+  // empty or all failed
+  if (totalCount === 0 || errorCount === totalCount) {
+    description = t('admin:slack_integration.integration_sentence.integration_is_not_complete');
+    iconClass = 'icon-info text-danger';
+    hrClass = 'border-danger admin-border-failed';
+  }
+  // all green
+  else if (errorCount === 0) {
+    description = t('admin:slack_integration.integration_sentence.integration_successful');
+    iconClass = 'fa fa-check text-success';
+    hrClass = 'border-success admin-border-success';
+  }
+  // some of them failed
+  else {
+    description = t('admin:slack_integration.integration_sentence.integration_some_ws_is_not_complete');
+    iconClass = 'fa fa-check text-warning';
+    hrClass = 'border-warning admin-border-failed';
+  }
+
+  return (
+    <BridgeCore
+      description={description}
+      iconClass={iconClass}
+      hrClass={hrClass}
+      withProxy={withProxy}
+    />
+  );
+};
+
+Bridge.propTypes = {
+  errorCount: PropTypes.number.isRequired,
+  totalCount: PropTypes.number.isRequired,
+  withProxy: PropTypes.bool,
+};
+
+export default Bridge;

+ 60 - 0
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.jsx

@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Bridge from './Bridge';
+
+const CustomBotWithProxyConnectionStatus = (props) => {
+  const { siteName, connectionStatuses } = props;
+
+  const connectionStatusValues = Object.values(connectionStatuses); // type: ConnectionStatus[]
+
+  const totalCount = connectionStatusValues.length;
+  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatusValues.error != null).length;
+
+  return (
+    <div className="d-flex justify-content-center my-5 bot-integration">
+
+      <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">
+          {connectionStatusValues.map((connectionStatus, i) => {
+            const workspaceName = connectionStatus.workspaceName || `Settings #${i}`;
+
+            return (
+              <div key={workspaceName} className="card slack-work-space-name-card">
+                <div className="m-2 text-center">
+                  <h5 className="font-weight-bold">{workspaceName}</h5>
+                  <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
+                </div>
+              </div>
+            );
+          })}
+        </div>
+      </div>
+
+      <div className="text-center w-25 mt-3">
+        <Bridge errorCount={errorCount} totalCount={totalCount} withProxy />
+      </div>
+
+      <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>
+        <div className="card-body text-center">
+          <div className="mt-5 border p-2 mx-3 bg-primary text-light">
+            {siteName}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+CustomBotWithProxyConnectionStatus.propTypes = {
+  siteName: PropTypes.string.isRequired,
+  connectionStatuses: PropTypes.object.isRequired,
+};
+
+export default CustomBotWithProxyConnectionStatus;

+ 0 - 89
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxyIntegrationCard.jsx

@@ -1,89 +0,0 @@
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import PropTypes from 'prop-types';
-
-const CustomBotWithProxyIntegrationCard = (props) => {
-  const { t } = useTranslation();
-
-  return (
-    <div className="d-flex justify-content-center my-5 bot-integration">
-
-      <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.slackWorkSpaces.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>
-
-      <div className="text-center w-25 mt-5">
-        {props.isSlackScopeSet && (
-          <p className="text-success small">
-            <i className="fa fa-check mr-1" />
-            {t('admin:slack_integration.integration_sentence.integration_successful')}
-          </p>
-        )}
-        {!props.isSlackScopeSet && (
-          <small
-            className="text-secondary"
-            // eslint-disable-next-line react/no-danger
-            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-          />
-        )}
-
-        <div className="pt-2">
-          <div className="position-relative mt-5">
-            <div className="circle position-absolute bg-primary border-light">
-              <p className="circle-inner text-light font-weight-bold">Proxy Server</p>
-            </div>
-            {props.isSlackScopeSet && (
-              <hr className="align-self-center border-success admin-border-success"></hr>
-            )}
-            {!props.isSlackScopeSet && (
-              <hr className="align-self-center border-danger admin-border-danger"></hr>
-            )}
-          </div>
-        </div>
-      </div>
-
-      <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>
-        <div className="card-body p-4 mb-5 text-center">
-          <div className="btn-group-vertical w-50">
-            {props.growiApps.map((growiApp) => {
-              return (
-                <button
-                  type="button"
-                  key={growiApp.name}
-                  className={growiApp.active ? 'btn btn-primary mb-3' : 'btn btn-outline-primary mb-3'}
-                >{growiApp.name}
-                </button>
-              );
-            })}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-CustomBotWithProxyIntegrationCard.propTypes = {
-  growiApps: PropTypes.array.isRequired,
-  slackWorkSpaces: PropTypes.array,
-  isSlackScopeSet: PropTypes.bool,
-};
-
-export default CustomBotWithProxyIntegrationCard;

+ 10 - 23
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -5,7 +5,7 @@ import loggerFactory from '@alias/logger';
 import AppContainer from '../../../services/AppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
-import CustomBotWithProxyIntegrationCard from './CustomBotWithProxyIntegrationCard';
+import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
 import WithProxyAccordions from './WithProxyAccordions';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
@@ -17,13 +17,9 @@ const CustomBotWithProxySettings = (props) => {
   } = props;
   const [newProxyServerUri, setNewProxyServerUri] = useState();
   const [integrationIdToDelete, setIntegrationIdToDelete] = useState(null);
+  const [siteName, setSiteName] = useState('');
   const { t } = useTranslation();
 
-  const workspaceNameObjects = Object.values(connectionStatuses);
-  const workspaceNames = workspaceNameObjects.map((w) => {
-    return w.workspaceName;
-  });
-
   useEffect(() => {
     if (proxyServerUri != null) {
       setNewProxyServerUri(proxyServerUri);
@@ -68,26 +64,19 @@ const CustomBotWithProxySettings = (props) => {
     }
   };
 
+  useEffect(() => {
+    const siteName = appContainer.config.crowi.title;
+    setSiteName(siteName);
+  }, [appContainer]);
+
   return (
     <>
       <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
 
       {/* TODO delete tmp props */}
-      <CustomBotWithProxyIntegrationCard
-        growiApps={
-          [
-            { name: 'siteName1', active: true },
-            { name: 'siteName2', active: false },
-            { name: 'siteName3', active: false },
-          ]
-        }
-        slackWorkSpaces={
-          [
-            { name: 'wsName1', active: true },
-            { name: 'wsName2', active: false },
-          ]
-        }
-        isSlackScopeSet
+      <CustomBotWithProxyConnectionStatus
+        siteName={siteName}
+        connectionStatuses={connectionStatuses}
       />
 
       <div className="form-group row my-4">
@@ -122,8 +111,6 @@ const CustomBotWithProxySettings = (props) => {
                   {t('admin:slack_integration.delete')}
                 </button>
               </div>
-              {proxyServerUri != null && workspaceNames[i] == null
-              && (<>Settings #{i + 1} <span className="text-danger">{t('admin:slack_integration.integration_failed')}</span></>)}
               <WithProxyAccordions
                 botType="customBotWithProxy"
                 slackAppIntegrationId={slackAppIntegration._id}

+ 57 - 0
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.jsx

@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Bridge from './Bridge';
+
+const CustomBotWithoutProxyConnectionStatus = (props) => {
+  const { siteName, connectionStatuses } = props;
+
+  const connectionStatusValues = Object.values(connectionStatuses); // type: ConnectionStatus[]
+
+  const totalCount = connectionStatusValues.length;
+  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatusValues.error != null).length;
+
+  let workspaceName;
+  if (totalCount > 0) {
+    workspaceName = connectionStatusValues[0].workspaceName;
+  }
+
+  return (
+    <div className="d-flex justify-content-center my-5 bot-integration">
+      <div className="card rounded shadow border-0 w-50 admin-bot-card mb-0">
+        <h5 className="card-title font-weight-bold mt-3 ml-4">Slack</h5>
+        <div className="card-body p-2 w-50 mx-auto">
+          {totalCount > 0 ? '' : (
+            <div className="card slack-work-space-name-card">
+              <div className="m-2 text-center">
+                <h5 className="font-weight-bold">
+                  {workspaceName != null ? workspaceName : 'Settings #1'}
+                </h5>
+                <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
+              </div>
+            </div>
+         )}
+        </div>
+      </div>
+
+      <div className="text-center w-25">
+        <Bridge errorCount={errorCount} totalCount={totalCount} />
+      </div>
+
+      <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
+        <h5 className="card-title font-weight-bold mt-3 ml-4">GROWI App</h5>
+        <div className="card-body p-4 mb-5 text-center">
+          <div className="border p-2 bg-primary text-light mx-5">
+            {siteName}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+CustomBotWithoutProxyConnectionStatus.propTypes = {
+  siteName: PropTypes.string.isRequired,
+  connectionStatuses: PropTypes.object.isRequired,
+};
+
+export default CustomBotWithoutProxyConnectionStatus;

+ 0 - 101
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxyIntegrationCard.jsx

@@ -1,101 +0,0 @@
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import PropTypes from 'prop-types';
-
-import { UncontrolledTooltip } from 'reactstrap';
-
-const IntegrationSuccess = () => {
-  const { t } = useTranslation();
-
-  return (
-    <>
-      <div className="d-none d-lg-block">
-        <p className="text-success small mt-5">
-          <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 id="integration-line-for-tooltip" className="d-block d-lg-none mt-5">
-        <i className="fa fa-check mr-1 text-success" />
-        <hr className="align-self-center admin-border-success border-success"></hr>
-      </div>
-      <UncontrolledTooltip placement="top" fade={false} target="integration-line-for-tooltip">
-        <small>
-          {t('admin:slack_integration.integration_sentence.integration_successful')}
-        </small>
-      </UncontrolledTooltip>
-    </>
-  );
-};
-
-const IntegrationFailed = () => {
-  const { t } = useTranslation();
-
-  return (
-    <>
-      <div className="d-none d-lg-block">
-        <p className="mt-4">
-          <small
-            className="text-danger m-0"
-          // eslint-disable-next-line react/no-danger
-            dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-          />
-        </p>
-        <hr className="align-self-center admin-border-danger border-danger"></hr>
-
-      </div>
-      <div id="integration-line-for-tooltip" className="d-block d-lg-none mt-5">
-        <i className="icon-info text-danger" />
-        <hr className="align-self-center admin-border-danger border-danger"></hr>
-      </div>
-      <UncontrolledTooltip placement="top" fade={false} target="integration-line-for-tooltip">
-        <small
-          className="m-0"
-        // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.integration_sentence.integration_is_not_complete') }}
-        />
-      </UncontrolledTooltip>
-    </>
-  );
-};
-
-const CustomBotWithoutProxyIntegrationCard = (props) => {
-
-  return (
-    <div className="d-flex justify-content-center my-5 bot-integration">
-      <div className="card rounded shadow border-0 w-50 admin-bot-card mb-0">
-        <h5 className="card-title font-weight-bold mt-3 ml-4">Slack</h5>
-        <div className="card-body p-2 w-50 mx-auto">
-          {props.isIntegrationSuccess && props.slackWSNameInWithoutProxy != null && (
-            <div className="card slack-work-space-name-card">
-              <div className="m-2 text-center">
-                <h5 className="font-weight-bold">{props.slackWSNameInWithoutProxy}</h5>
-                <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
-              </div>
-            </div>
-          )}
-        </div>
-      </div>
-
-      <div className="text-center w-25">
-        {props.isIntegrationSuccess ? <IntegrationSuccess /> : <IntegrationFailed />}
-      </div>
-
-      <div className="card rounded-lg shadow border-0 w-50 admin-bot-card mb-0">
-        <h5 className="card-title font-weight-bold mt-3 ml-4">GROWI App</h5>
-        <div className="card-body p-4 mb-5 text-center">
-          <div className="btn btn-primary">{ props.siteName }</div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-CustomBotWithoutProxyIntegrationCard.propTypes = {
-  siteName: PropTypes.string.isRequired,
-  slackWSNameInWithoutProxy: PropTypes.string,
-  isIntegrationSuccess: PropTypes.bool,
-};
-
-export default CustomBotWithoutProxyIntegrationCard;

+ 8 - 7
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -5,11 +5,11 @@ import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
-import CustomBotWithoutProxyIntegrationCard from './CustomBotWithoutProxyIntegrationCard';
+import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
 const CustomBotWithoutProxySettings = (props) => {
-  const { appContainer, onResetSettings } = props;
+  const { appContainer, onResetSettings, connectionStatuses } = props;
   const { t } = useTranslation();
 
   const [siteName, setSiteName] = useState('');
@@ -54,10 +54,9 @@ const CustomBotWithoutProxySettings = (props) => {
     <>
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_integration')}</h2>
 
-      <CustomBotWithoutProxyIntegrationCard
+      <CustomBotWithoutProxyConnectionStatus
         siteName={siteName}
-        slackWSNameInWithoutProxy={props.slackWSNameInWithoutProxy}
-        isIntegrationSuccess={isIntegrationSuccess}
+        connectionStatuses={connectionStatuses}
       />
 
       <h2 className="admin-setting-header">{t('admin:slack_integration.integration_procedure')}</h2>
@@ -70,8 +69,8 @@ const CustomBotWithoutProxySettings = (props) => {
       >{t('admin:slack_integration.reset')}
       </button>
       )}
-
       <div className="my-5 mx-3">
+        {/* {isConnectedFailed && (<>Settings #1 <span className="text-danger">{t('admin:slack_integration.integration_failed')}</span></>)} */}
         <CustomBotWithoutProxySettingsAccordion
           {...props}
           activeStep={botInstallationStep.CREATE_BOT}
@@ -81,7 +80,6 @@ const CustomBotWithoutProxySettings = (props) => {
           testChannel={testChannel}
           onTestFormSubmitted={testConnection}
           inputTestChannelHandler={inputTestChannelHandler}
-
         />
       </div>
       <DeleteSlackBotSettingsModal
@@ -99,14 +97,17 @@ const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWit
 CustomBotWithoutProxySettings.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
+
   slackSigningSecret: PropTypes.string,
   slackSigningSecretEnv: PropTypes.string,
   slackBotToken: PropTypes.string,
   slackBotTokenEnv: PropTypes.string,
+
   isRgisterSlackCredentials: PropTypes.bool,
   isIntegrationSuccess: PropTypes.bool,
   slackWSNameInWithoutProxy: PropTypes.string,
   onResetSettings: PropTypes.func,
+  connectionStatuses: PropTypes.object.isRequired,
 };
 
 export default CustomBotWithoutProxySettingsWrapper;

+ 14 - 3
src/client/js/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -35,14 +35,15 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
     <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>
           {props.isResetAll && (
             <>
+              <i className="icon-fw icon-fire" />
               {t('admin:slack_integration.reset_all_settings')}
             </>
           )}
           {!props.isResetAll && (
             <>
+              <i className="icon-trash mr-1" />
               {t('admin:slack_integration.delete_slackbot_settings')}
             </>
           )}
@@ -66,8 +67,18 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
       <ModalFooter>
         <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
         <Button color="danger" onClick={deleteSlackCredentialsHandler}>
-          <i className="icon icon-fire"></i>
-          {t('admin:slack_integration.reset')}
+          {props.isResetAll && (
+            <>
+              <i className="icon icon-fire"></i>
+              {t('admin:slack_integration.reset')}
+            </>
+          )}
+          {!props.isResetAll && (
+            <>
+              <i className="icon-trash mr-1" />
+              {t('admin:slack_integration.delete')}
+            </>
+          )}
         </Button>
       </ModalFooter>
     </Modal>

+ 17 - 20
src/client/js/components/Admin/SlackIntegration/OfficialBotSettings.jsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
 import AppContainer from '../../../services/AppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
-import CustomBotWithProxyIntegrationCard from './CustomBotWithProxyIntegrationCard';
+import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
 import WithProxyAccordions from './WithProxyAccordions';
 import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
@@ -13,8 +13,9 @@ const logger = loggerFactory('growi:SlackBotSettings');
 
 const OfficialBotSettings = (props) => {
   const {
-    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn,
+    appContainer, slackAppIntegrations, proxyServerUri, onClickAddSlackWorkspaceBtn, connectionStatuses,
   } = props;
+  const [siteName, setSiteName] = useState('');
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
   const { t } = useTranslation();
 
@@ -32,6 +33,7 @@ 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
@@ -42,6 +44,7 @@ const OfficialBotSettings = (props) => {
       logger(err);
     }
   };
+  */
 
   const deleteSlackAppIntegrationHandler = async() => {
     try {
@@ -67,25 +70,17 @@ const OfficialBotSettings = (props) => {
     }
   };
 
+  useEffect(() => {
+    const siteName = appContainer.config.crowi.title;
+    setSiteName(siteName);
+  }, [appContainer]);
+
   return (
     <>
       <h2 className="admin-setting-header">{t('admin:slack_integration.official_bot_integration')}</h2>
-      {/* TODO delete tmp props */}
-      <CustomBotWithProxyIntegrationCard
-        growiApps={
-          [
-            { name: 'siteName1', active: true },
-            { name: 'siteName2', active: false },
-            { name: 'siteName3', active: false },
-          ]
-        }
-        slackWorkSpaces={
-          [
-            { name: 'wsName1', active: true },
-            { name: 'wsName2', active: false },
-          ]
-        }
-        isSlackScopeSet
+      <CustomBotWithProxyConnectionStatus
+        siteName={siteName}
+        connectionStatuses={connectionStatuses}
       />
 
       <div className="form-group row my-4">
@@ -110,7 +105,7 @@ const OfficialBotSettings = (props) => {
         {slackAppIntegrations.map((slackAppIntegration) => {
           const { tokenGtoP, tokenPtoG } = slackAppIntegration;
           return (
-            <React.Fragment key={slackAppIntegration.id}>
+            <React.Fragment key={slackAppIntegration._id}>
               <div className="d-flex justify-content-end">
                 <button
                   className="my-3 btn btn-outline-danger"
@@ -124,7 +119,7 @@ const OfficialBotSettings = (props) => {
               <WithProxyAccordions
                 botType="officialBot"
                 slackAppIntegrationId={slackAppIntegration._id}
-                discardTokenHandler={() => discardTokenHandler(tokenGtoP, tokenPtoG)}
+                onClickGenerateTokenBtn={generateTokenHandler}
                 tokenGtoP={tokenGtoP}
                 tokenPtoG={tokenPtoG}
               />
@@ -164,6 +159,8 @@ OfficialBotSettings.propTypes = {
   slackAppIntegrations: PropTypes.array,
   proxyServerUri: PropTypes.string,
   onClickAddSlackWorkspaceBtn: PropTypes.func,
+  connectionStatuses: PropTypes.object.isRequired,
+
 };
 
 export default OfficialBotSettingsWrapper;

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

@@ -41,12 +41,7 @@ const SlackIntegration = (props) => {
       if (data.connectionStatuses == null) {
         data.connectionStatuses = {};
       }
-      // if (data.connectionStatuses != null) {
-      // TODO fix
-      // const { workspaceName } = data.connectionStatuses[slackBotToken];
-      // setSlackWSNameInWithoutProxy(workspaceName);
-      // setConnectionStatuses(data.connectionStatuses);
-      // }
+
       setConnectionStatuses(data.connectionStatuses);
       setCurrentBotType(data.currentBotType);
       setSlackSigningSecret(slackSigningSecret);
@@ -143,6 +138,7 @@ const SlackIntegration = (props) => {
           slackAppIntegrations={slackAppIntegrations}
           proxyServerUri={proxyServerUri}
           onClickAddSlackWorkspaceBtn={createSlackIntegrationData}
+          connectionStatuses={connectionStatuses}
         />
       );
       break;
@@ -159,6 +155,7 @@ const SlackIntegration = (props) => {
           onSetSlackBotToken={setSlackBotToken}
           onResetSettings={resetWithOutSettings}
           fetchSlackIntegrationData={fetchSlackIntegrationData}
+          connectionStatuses={connectionStatuses}
         />
       );
       break;
@@ -200,19 +197,13 @@ const SlackIntegration = (props) => {
           </a>
         </h2>
 
-        <div className="d-flex justify-content">
-          <div className="mr-auto">
-            {t('admin:slack_integration.selecting_bot_types.selecting_bot_type')}
-          </div>
-
-          {(currentBotType != null) && (
-            <button
-              className="mx-3 btn btn-outline-danger flex-end"
-              type="button"
-              onClick={() => setIsDeleteConfirmModalShown(true)}
-            >{t('admin:slack_integration.reset_all_settings')}
-            </button>
-          )}
+        <div className="d-flex justify-content-end">
+          <button
+            className="btn btn-outline-danger"
+            type="button"
+            onClick={() => setIsDeleteConfirmModalShown(true)}
+          >{t('admin:slack_integration.reset_all_settings')}
+          </button>
         </div>
 
         <div className="row my-5 flex-wrap-reverse justify-content-center">

+ 27 - 11
src/client/styles/scss/_admin.scss

@@ -122,31 +122,47 @@ $slack-work-space-name-card-border: #efc1f6;
     .admin-bot-card {
       border-radius: 8px !important;
     }
-    .admin-border-danger {
+    .admin-border-failed {
       border-style: dashed;
       border-width: 2px;
     }
     .admin-border-success {
       border-width: 3px;
     }
-    .circle {
-      top: 50%;
+
+    .grw-bridge-proxy-circle {
       left: 50%;
       width: 100px;
       height: 100px;
       border: 13px solid;
       border-radius: 50%;
-      -webkit-transform: translate(-50%, -50%);
-      -ms-transform: translate(-50%, -50%);
       transform: translate(-50%, -50%);
+
+      .circle-inner {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
     }
-    .circle-inner {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      -webkit-transform: translate(-50%, -50%);
-      transform: translate(-50%, -50%);
+
+    // switch layout for Bridge component
+    .grw-bridge-container {
+      .label {
+        @extend .mt-5;
+      }
+
+      // with ProxyCircle
+      &.with-proxy {
+        .label {
+          @extend .mt-0;
+        }
+        .hr-container {
+          margin-top: 65px;
+        }
+      }
     }
+
     .slack-work-space-name-card {
       background-color: $slack-work-space-name-card-background;
       border: 1px solid $slack-work-space-name-card-border;

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

@@ -73,6 +73,8 @@ module.exports = (crowi) => {
   };
 
   async function resetAllBotSettings() {
+    await SlackAppIntegration.deleteMany();
+
     const params = {
       'slackbot:currentBotType': null,
       'slackbot:signingSecret': null,