diff --git a/engine/apps/slack/views.py b/engine/apps/slack/views.py index 217b0f7c..8d4ca3c1 100644 --- a/engine/apps/slack/views.py +++ b/engine/apps/slack/views.py @@ -501,8 +501,8 @@ class SlackEventApiEndpointView(APIView): return text = ( - "Your Grafana account is not connected to your Slack account. :flushed:\n" - "That's very easy to fix. Please go to the *Grafana* -> *OnCall* -> *Users*, " + "The information in workspace is read-only. To be able to intercat with OnCall alert groups you need to connect a personal account.\n" + "Please go to the *Grafana* -> *OnCall* -> *Users*, " "choose *your profile* and click the *connect* button.\n" ":rocket: :rocket: :rocket:" ) diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css new file mode 100644 index 00000000..0083dc6f --- /dev/null +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css @@ -0,0 +1,11 @@ +.slack-infoblock { + width: 725px; +} + +.slack-infoblock input { + color: var(--primary-text-link); +} + +.slack-icon { + width: 60px; +} diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx new file mode 100644 index 00000000..9b4638f1 --- /dev/null +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx @@ -0,0 +1,71 @@ +import React, { useCallback, useState, FC } from 'react'; + +import { Button, VerticalGroup, Icon, Field, Input } from '@grafana/ui'; +import cn from 'classnames/bind'; +import { observer } from 'mobx-react'; + +import { SlackNewIcon } from 'icons'; +import Block from 'components/GBlock/Block'; +import Text from 'components/Text/Text'; + +import styles from './SlackInstructions.module.css'; + +const cx = cn.bind(styles); + +interface SlackInstructionsProps {} +/* This component will be used when we will work on moving ENV variables to chat-ops, but we need to do work on backend side first */ +const SlackInstructions: FC = observer((props) => { + return ( +
+ + Connect Slack workspace + + + + + You can manage incidents in your Slack workspace. + Before start you need to connect your Slack bot to Grafana OnCall. + + For bot creating instructions and additional information please read{' '} + + our documentation + + {' '} + + + Setup environment + + Create OnCall Slack bot using{' '} + + our instructions + {' '} + and fill out app credentials below. + +
+ + {}} defaultValue={'appId'} /> + + + {}} defaultValue={'clientsecret'} /> + + + {}} defaultValue={'signingsecret'} /> + + + {}} defaultValue={'https://'} /> + +
+ + + Your host to Slack must start with “https://” and be publicly available (meaning + that it can be reached by Slack servers). If your host is private or local, you can use redirecting services + like Ngrok. + + + +
+
+ ); +}); + +export default SlackInstructions; diff --git a/grafana-plugin/src/containers/SlackIntegrationButton/SlackIntegrationButton.tsx b/grafana-plugin/src/containers/SlackIntegrationButton/SlackIntegrationButton.tsx index 2909b2f4..b414fce5 100644 --- a/grafana-plugin/src/containers/SlackIntegrationButton/SlackIntegrationButton.tsx +++ b/grafana-plugin/src/containers/SlackIntegrationButton/SlackIntegrationButton.tsx @@ -64,7 +64,7 @@ const SlackIntegrationButton = observer((props: { className: string; disabled?: disabled={disabled} onClick={onInstallModalCallback} > - Install Slack integration + Connect Slack {showModal && } @@ -81,8 +81,8 @@ const SlackModal = (props: SlackModalProps) => { const { onHide, onConfirm } = props; return ( - -
+ +
You can view your Slack Workspace at the top-right corner after you are redirected. It should be a Workspace with App Bot installed:
diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.module.css b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.module.css index ce2afbc6..53d1112f 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.module.css +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.module.css @@ -2,3 +2,13 @@ display: flex; justify-content: flex-end; } + +.slack-infoblock { + text-align: center; + width: 725px; +} + +.external-link-style { + margin-right: 4px; + align-self: baseline; +} \ No newline at end of file diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx index 4edd5ab5..27d44d37 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx @@ -1,9 +1,11 @@ import React, { useCallback } from 'react'; -import { Button, VerticalGroup } from '@grafana/ui'; +import { Button, VerticalGroup, Icon } from '@grafana/ui'; import cn from 'classnames/bind'; import Text from 'components/Text/Text'; +import Block from 'components/GBlock/Block'; +import { SlackNewIcon } from 'icons'; import { useStore } from 'state/useStore'; import styles from './SlackTab.module.css'; @@ -18,20 +20,32 @@ export const SlackTab = () => { }, [slackStore]); return ( - - - You can view your Slack Workspace at the top-right corner after you are redirected. It should be a Workspace - with App Bot installed: - - -
- -
+ + + + + + Personal Slack connection will allow you to manage incidents in your connected team Internal Slack + workspace. + + To setup personal Slack click the button below, choose workspace and click Allow. + + + More details in{' '} + + our documentation + + + + + + + ); }; diff --git a/grafana-plugin/src/icons/index.tsx b/grafana-plugin/src/icons/index.tsx index 63f0ccb9..bcdcbab3 100644 --- a/grafana-plugin/src/icons/index.tsx +++ b/grafana-plugin/src/icons/index.tsx @@ -293,3 +293,39 @@ export const TelegramColorIcon = () => { ); }; +export const SlackNewIcon = (props: IconProps) => ( + + + + + + + + + + +); diff --git a/grafana-plugin/src/img/slack_instructions.png b/grafana-plugin/src/img/slack_instructions.png new file mode 100644 index 00000000..0093b533 Binary files /dev/null and b/grafana-plugin/src/img/slack_instructions.png differ diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.module.css b/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.module.css index b9ab506c..fe7c1bef 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.module.css +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.module.css @@ -15,3 +15,17 @@ margin-bottom: 20px; border-bottom: 1px solid rgba(204, 204, 220, 0.25); } + +.slack-infoblock { + text-align: center; + width: 725px; +} + +.external-link-style { + margin-right: 4px; + align-self: baseline; +} + +.team_workspace { + height: 30px; +} \ No newline at end of file diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx index 67abb7d5..b372c33e 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx @@ -1,16 +1,16 @@ import React, { Component } from 'react'; -import { Field, HorizontalGroup, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; +import { Field, HorizontalGroup, LoadingPlaceholder, VerticalGroup, Icon, Button } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; +import { SlackNewIcon } from 'icons'; +import Block from 'components/GBlock/Block'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; -import Tutorial from 'components/Tutorial/Tutorial'; -import { TutorialStep } from 'components/Tutorial/Tutorial.types'; +import WithConfirm from 'components/WithConfirm/WithConfirm'; import GSelect from 'containers/GSelect/GSelect'; import RemoteSelect from 'containers/RemoteSelect/RemoteSelect'; -import SlackIntegrationButton from 'containers/SlackIntegrationButton/SlackIntegrationButton'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { PRIVATE_CHANNEL_NAME } from 'models/slack_channel/slack_channel.config'; import { SlackChannel } from 'models/slack_channel/slack_channel.types'; @@ -25,16 +25,26 @@ const cx = cn.bind(styles); interface SlackProps extends WithStoreProps {} -interface SlackState {} +interface SlackState { + showENVVariablesButton: boolean; +} @observer class SlackSettings extends Component { - state: SlackState = {}; + state: SlackState = { + showENVVariablesButton: false, + }; componentDidMount() { + this.getSlackLiveSettings(); this.update(); } + handleOpenSlackInstructions = () => { + const { store } = this.props; + store.slackStore.installSlackIntegration(); + }; + update = () => { const { store } = this.props; @@ -42,6 +52,32 @@ class SlackSettings extends Component { store.slackStore.updateSlackSettings(); }; + getSlackLiveSettings = async () => { + const { store } = this.props; + const slackClientOAUTH = await store.globalSettingStore.getGlobalSettingItemByName('SLACK_CLIENT_OAUTH_ID'); + const slackClientOAUTHSecret = await store.globalSettingStore.getGlobalSettingItemByName( + 'SLACK_CLIENT_OAUTH_SECRET' + ); + const slackRedirectHost = await store.globalSettingStore.getGlobalSettingItemByName( + 'SLACK_INSTALL_RETURN_REDIRECT_HOST' + ); + const slackSigningSecret = await store.globalSettingStore.getGlobalSettingItemByName('SLACK_SIGNING_SECRET'); + + console.log('slackClientOAUTH', slackClientOAUTH?.error); + console.log('slackClientOAUTHSecret', slackClientOAUTHSecret?.error); + console.log('slackRedirectHost', slackRedirectHost?.error); + console.log('slackSigningSecret', slackSigningSecret?.error); + if ( + slackClientOAUTH?.error || + slackClientOAUTHSecret?.error || + slackRedirectHost?.error || + slackSigningSecret?.error + ) { + console.log('BLA BLA'); + this.setState({ showENVVariablesButton: true }); + } + }; + render() { const { store } = this.props; const { teamStore } = store; @@ -59,34 +95,47 @@ class SlackSettings extends Component { return (
- - Slack - -
- - - - - +
+ Slack
- + + + +
+ {store.teamStore.currentTeam.slack_team_identity?.cached_name} +
+
+ + + + + +
+ + + + + +
+
+
+ Additional settings - + {
- - Remove integration - -
); }; + renderSlackWorkspace = () => { + const { store } = this.props; + return {store.teamStore.currentTeam.slack_team_identity?.cached_name}; + }; + + renderSlackChannels = () => { + const { store } = this.props; + return ( + + + + ); + }; + + renderActionButtons = () => { + + + + + ; + }; + + removeSlackIntegration = () => { + const { store } = this.props; + store.slackStore.removeSlackIntegration().then(() => { + store.teamStore.loadCurrentTeam(); + }); + }; + getSlackSettingsChangeHandler = (field: string) => { const { store } = this.props; const { slackStore } = store; @@ -138,28 +224,49 @@ class SlackSettings extends Component { renderSlackStub = () => { const { store } = this.props; - + const { showENVVariablesButton } = this.state; + const isLiveSettingAvailable = store.hasFeature(AppFeature.LiveSettings) && showENVVariablesButton; return ( - + Connect Slack workspace + - - Bring the whole incident lifecycle to Slack, from alerts, monitoring, escalations to resolution notes and - reports. + + + Slack connection will allow you to manage incidents in your team Slack workspace. +
+ After a basic workspace connection, your team members need to connect their personal Slack accounts in + order to be allowed to manage incidents.
- - - - {store.hasFeature(AppFeature.LiveSettings) && ( + {isLiveSettingAvailable && ( - Before installing check ENV variables related - to Slack please + For bot creating instructions and additional information please read{' '} + + our documentation + )} +
- } - /> +
+ {isLiveSettingAvailable ? ( + + + + ) : ( + + + + + + + )} + ); }; }