From 1708410f215dc9893184809515755064ddf32893 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Mon, 12 Sep 2022 14:28:46 +0200 Subject: [PATCH 01/50] WIP slack changes --- .../SlackIntegrationButton/SlackIntegrationButton.tsx | 6 +++--- .../chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) 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/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx index 67abb7d5..8d88622b 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 @@ -144,7 +144,8 @@ class SlackSettings extends Component { step={TutorialStep.Slack} title={ - + Connect your Slack workspace + Bring the whole incident lifecycle to Slack, from alerts, monitoring, escalations to resolution notes and reports. From c103464981d5adadc53adc8da684d9b300a41ff1 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Wed, 28 Sep 2022 10:20:18 +0200 Subject: [PATCH 02/50] WIP. Slack redesign work --- .../SlackInstructions.module.css | 11 +++ .../SlackInstructions/SlackInstructions.tsx | 69 ++++++++++++++++++ grafana-plugin/src/icons/index.tsx | 38 ++++++++++ .../SlackSettings/SlackSettings.module.css | 5 ++ .../tabs/SlackSettings/SlackSettings.tsx | 71 +++++++++++++------ 5 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css create mode 100644 grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx 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..2f95b63e --- /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; +} \ No newline at end of file diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx new file mode 100644 index 00000000..54baf756 --- /dev/null +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx @@ -0,0 +1,69 @@ +import React, { useCallback, useState, FC } from 'react'; +import { SlackNewIcon } from '../../icons'; +import { Button, VerticalGroup, Icon, Field, Input } from '@grafana/ui'; +import { observer } from 'mobx-react'; +import cn from 'classnames/bind'; + +import Text from 'components/Text/Text'; +import Block from 'components/GBlock/Block'; + +import styles from './SlackInstructions.module.css'; + +const cx = cn.bind(styles); + +interface SlackInstructionsProps {} + +const SlackInstructions: FC = observer((props) => { + return ( +
+ + {/* + Setup 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/icons/index.tsx b/grafana-plugin/src/icons/index.tsx index ffc6f75c..e54c5b56 100644 --- a/grafana-plugin/src/icons/index.tsx +++ b/grafana-plugin/src/icons/index.tsx @@ -269,3 +269,41 @@ export const IsOncallIcon = (props: IsOncallIconProps) => { ); }; + +export const SlackNewIcon = (props: IconProps) => ( +
Test
+ // + // + // + // + // + // + // + // + // + // +); 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..c1a97c95 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,8 @@ margin-bottom: 20px; border-bottom: 1px solid rgba(204, 204, 220, 0.25); } + +.slack-infoblock { + text-align: center; + width: 725px; +} \ 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 8d88622b..b1f91acf 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,6 +1,6 @@ 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'; @@ -8,10 +8,12 @@ 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 Block from 'components/GBlock/Block'; 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 SlackInstructions from 'containers/SlackInstructions/SlackInstructions'; import { PRIVATE_CHANNEL_NAME } from 'models/slack_channel/slack_channel.config'; import { SlackChannel } from 'models/slack_channel/slack_channel.types'; import { AppFeature } from 'state/features'; @@ -35,6 +37,11 @@ class SlackSettings extends Component { this.update(); } + handleOpenSlackInstructions = () => { + const { store } = this.props; + store.slackStore.installSlackIntegration(); + }; + update = () => { const { store } = this.props; @@ -140,27 +147,51 @@ class SlackSettings extends Component { const { store } = this.props; return ( - - Connect your Slack workspace - - Bring the whole incident lifecycle to Slack, from alerts, monitoring, escalations to resolution notes and - reports. - + // + // Connect your Slack workspace + // + // Bring the whole incident lifecycle to Slack, from alerts, monitoring, escalations to resolution notes and + // reports. + // - + // - {store.hasFeature(AppFeature.LiveSettings) && ( - - Before installing check ENV variables related - to Slack please - - )} -
- } - /> + // {store.hasFeature(AppFeature.LiveSettings) && ( + // + // Before installing check ENV variables related + // to Slack please + // + // )} + // + // } + // /> + + // + // Connect Slack workspace + // + // + // + // 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. + // + // + // + // + // + // + <> + + ); }; } From 06a5b5570a0c2932f7788c7d94cb2273d2626ee4 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 11 Oct 2022 11:35:51 +0200 Subject: [PATCH 03/50] Slack integration redesign. Changed the message from backend as well --- engine/apps/slack/views.py | 4 +- .../SlackInstructions.module.css | 10 +- .../SlackInstructions/SlackInstructions.tsx | 22 +- .../parts/tabs/SlackTab/SlackTab.module.css | 10 + .../parts/tabs/SlackTab/SlackTab.tsx | 44 ++-- grafana-plugin/src/icons/index.tsx | 69 +++-- grafana-plugin/src/img/slack_instructions.png | Bin 0 -> 54320 bytes .../SlackSettings/SlackSettings.module.css | 9 + .../tabs/SlackSettings/SlackSettings.tsx | 237 ++++++++++++------ 9 files changed, 257 insertions(+), 148 deletions(-) create mode 100644 grafana-plugin/src/img/slack_instructions.png 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 index 2f95b63e..0083dc6f 100644 --- a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.module.css @@ -1,11 +1,11 @@ .slack-infoblock { - width: 725px; + width: 725px; } -.slack-infoblock input{ - color: var(--primary-text-link) +.slack-infoblock input { + color: var(--primary-text-link); } .slack-icon { - width: 60px; -} \ No newline at end of file + width: 60px; +} diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx index 54baf756..9b4638f1 100644 --- a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx @@ -1,26 +1,28 @@ import React, { useCallback, useState, FC } from 'react'; -import { SlackNewIcon } from '../../icons'; -import { Button, VerticalGroup, Icon, Field, Input } from '@grafana/ui'; -import { observer } from 'mobx-react'; -import cn from 'classnames/bind'; -import Text from 'components/Text/Text'; +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 (
- - {/* - Setup Slack workspace + + Connect Slack workspace + + You can manage incidents in your Slack workspace. Before start you need to connect your Slack bot to Grafana OnCall. @@ -61,7 +63,7 @@ const SlackInstructions: FC = observer((props) => { - */} +
); }); 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 38630531..41239fa4 100644 --- a/grafana-plugin/src/icons/index.tsx +++ b/grafana-plugin/src/icons/index.tsx @@ -271,39 +271,38 @@ export const IsOncallIcon = (props: IsOncallIconProps) => { }; export const SlackNewIcon = (props: IconProps) => ( -
Test
- // - // - // - // - // - // - // - // - // - // + + + + + + + + + + ); diff --git a/grafana-plugin/src/img/slack_instructions.png b/grafana-plugin/src/img/slack_instructions.png new file mode 100644 index 0000000000000000000000000000000000000000..0093b533296040529a162fdb8f8a08d4ab213c3f GIT binary patch literal 54320 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 { - state: SlackState = {}; + state: SlackState = { + showENVVariablesButton: false, + }; componentDidMount() { + this.getSlackLiveSettings(); this.update(); } @@ -49,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; @@ -66,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; @@ -145,53 +224,49 @@ class SlackSettings extends Component { renderSlackStub = () => { const { store } = this.props; - + const { showENVVariablesButton } = this.state; + const isLiveSettingAvailable = store.hasFeature(AppFeature.LiveSettings) && showENVVariablesButton; return ( - // - // Connect your Slack workspace - // - // Bring the whole incident lifecycle to Slack, from alerts, monitoring, escalations to resolution notes and - // reports. - // - - // - - // {store.hasFeature(AppFeature.LiveSettings) && ( - // - // Before installing check ENV variables related - // to Slack please - // - // )} - // - // } - // /> - - // - // Connect Slack workspace - // - // - // - // 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. - // - // - // - // - // - // - <> - - + + Connect Slack workspace + + + + + 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. +
+ {isLiveSettingAvailable && ( + + For bot creating instructions and additional information please read{' '} + + our documentation + + + )} + +
+
+ {isLiveSettingAvailable ? ( + + + + ) : ( + + + + + + + )} +
); }; } From 1ef192704086312ff8584c36cc00c9de2cd87df0 Mon Sep 17 00:00:00 2001 From: alyssa wada Date: Tue, 11 Oct 2022 16:20:19 -0600 Subject: [PATCH 04/50] Docs file restucture - Batch 1 --- docs/sources/{getting-started => get-started}/_index.md | 0 docs/sources/integrations/_index.md | 4 ++-- docs/sources/integrations/available-integrations/_index.md | 3 ++- .../index.md} | 7 ++++--- .../index.md} | 7 ++++--- .../configure-webhook/index.md} | 3 ++- .../{add-zabbix.md => configure-zabbix/index.md} | 7 ++++--- .../chatops-integrations}/_index.md | 7 ++++--- .../chatops-integrations/configure-slack/index.md} | 1 + .../chatops-integrations/configure-teams/index.md} | 1 + .../chatops-integrations/configure-telegram/index.md} | 1 + 11 files changed, 25 insertions(+), 16 deletions(-) rename docs/sources/{getting-started => get-started}/_index.md (100%) rename docs/sources/integrations/available-integrations/{add-alertmanager.md => configure-alertmanager/index.md} (91%) rename docs/sources/integrations/available-integrations/{add-grafana-alerting.md => configure-grafana-alerting/index.md} (93%) rename docs/sources/integrations/{add-webhook-integration.md => available-integrations/configure-webhook/index.md} (95%) rename docs/sources/integrations/available-integrations/{add-zabbix.md => configure-zabbix/index.md} (95%) rename docs/sources/{chat-options => integrations/chatops-integrations}/_index.md (82%) rename docs/sources/{chat-options/configure-slack.md => integrations/chatops-integrations/configure-slack/index.md} (98%) rename docs/sources/{chat-options/configure-teams.md => integrations/chatops-integrations/configure-teams/index.md} (97%) rename docs/sources/{chat-options/configure-telegram.md => integrations/chatops-integrations/configure-telegram/index.md} (97%) diff --git a/docs/sources/getting-started/_index.md b/docs/sources/get-started/_index.md similarity index 100% rename from docs/sources/getting-started/_index.md rename to docs/sources/get-started/_index.md diff --git a/docs/sources/integrations/_index.md b/docs/sources/integrations/_index.md index 354257a8..fde6b402 100644 --- a/docs/sources/integrations/_index.md +++ b/docs/sources/integrations/_index.md @@ -10,12 +10,12 @@ keywords: - amixr - oncall - integrations -title: Connect to Grafana OnCall +title: Grafana OnCall integrations canonical: "https://grafana.com/docs/oncall/latest/integrations/" weight: 500 --- -# Connect to Grafana OnCall +# Grafana OnCall integrations Integrations allow you to connect monitoring systems of your choice to send alerts to Grafana OnCall. Regardless of where your alerts originate, you can configure alerts to be sent to Grafana OnCall for alert escalation and notification. Grafana OnCall receives alerts in JSON format via a POST request, OnCall then parses alert data using preconfigured alert templates to determine alert grouping, apply routes, and determine correct escalation. diff --git a/docs/sources/integrations/available-integrations/_index.md b/docs/sources/integrations/available-integrations/_index.md index c3127657..0f2c0b97 100644 --- a/docs/sources/integrations/available-integrations/_index.md +++ b/docs/sources/integrations/available-integrations/_index.md @@ -2,6 +2,7 @@ aliases: - /docs/grafana-cloud/oncall/integrations/add-integration/ - /docs/oncall/latest/integrations/available-integrations/ + - /docs/grafana-cloud/oncall/integrations/available-integration/ keywords: - Grafana Cloud - Alerts @@ -14,7 +15,7 @@ canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integr weight: 100 --- -# Currently available integrations +# Available integrations Grafana OnCall can connect directly to the monitoring services where your alerts originate. All currently available integrations are listed in the Grafana OnCall **Create Integration** section. diff --git a/docs/sources/integrations/available-integrations/add-alertmanager.md b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md similarity index 91% rename from docs/sources/integrations/available-integrations/add-alertmanager.md rename to docs/sources/integrations/available-integrations/configure-alertmanager/index.md index e3a80c1c..5787ecc0 100644 --- a/docs/sources/integrations/available-integrations/add-alertmanager.md +++ b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md @@ -1,7 +1,8 @@ --- aliases: - /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ - - /docs/oncall/latest/integrations/available-integrations /add-alertmanager/ + - /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ + - /docs/oncall/latest/integrations/available-integrations/add-alertmanager/ keywords: - Grafana Cloud - Alerts @@ -9,12 +10,12 @@ keywords: - on-call - Alertmanager - Prometheus -title: Connect Alert Manager to Grafana OnCall +title: AlertManager integration for Grafana OnCall canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/" weight: 300 --- -# Connect AlertManager to Grafana OnCall +# AlertManager integration for Grafana OnCall The AlertManager integration for Grafana OnCall handles alerts sent by client applications such as the Prometheus server. diff --git a/docs/sources/integrations/available-integrations/add-grafana-alerting.md b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md similarity index 93% rename from docs/sources/integrations/available-integrations/add-grafana-alerting.md rename to docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md index e3af0a90..4b18f530 100644 --- a/docs/sources/integrations/available-integrations/add-grafana-alerting.md +++ b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md @@ -1,19 +1,20 @@ --- aliases: - /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ - - /docs/oncall/latest/integrations/available-integrations /add-grafana-alerting/ + - /docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ + - /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ keywords: - Grafana Cloud - Alerts - Notifications - on-call - Prometheus -title: Connect Grafana Alerting to Grafana OnCall +title: Grafana Alerting integration for Grafana OnCall canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/" weight: 100 --- -# Connect Grafana Alerting to Grafana OnCall +# Grafana Alerting integration for Grafana OnCall Grafana Alerting for Grafana OnCall can be set up using two methods: diff --git a/docs/sources/integrations/add-webhook-integration.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md similarity index 95% rename from docs/sources/integrations/add-webhook-integration.md rename to docs/sources/integrations/available-integrations/configure-webhook/index.md index f1d96eed..774d7ae0 100644 --- a/docs/sources/integrations/add-webhook-integration.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/integrations/add-webhook-integration/ + - /docs/oncall/latest/integrations/available-integrations/configure-webhook/ keywords: - Grafana Cloud - Alerts @@ -13,7 +14,7 @@ canonical: "https://grafana.com/docs/oncall/latest/integrations/add-webhook-inte weight: 700 --- -# Configure Webhook integrations for Grafana OnCall +# Webhook integrations for Grafana OnCall Grafana OnCall directly supports many integrations, those that aren’t currently listed in the Integrations menu can be connected using the webhook integration and configured alert templates. diff --git a/docs/sources/integrations/available-integrations/add-zabbix.md b/docs/sources/integrations/available-integrations/configure-zabbix/index.md similarity index 95% rename from docs/sources/integrations/available-integrations/add-zabbix.md rename to docs/sources/integrations/available-integrations/configure-zabbix/index.md index cfe663a1..cc03ca78 100644 --- a/docs/sources/integrations/available-integrations/add-zabbix.md +++ b/docs/sources/integrations/available-integrations/configure-zabbix/index.md @@ -1,18 +1,19 @@ --- aliases: - - /docs/oncall/latest/integrations/available-integrations /add-zabbix/ + - /docs/oncall/latest/integrations/available-integrations/add-zabbix/ + - /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ keywords: - Grafana Cloud - Alerts - Notifications - on-call - Zabbix -title: Connect Zabbix to Grafana OnCall +title: Zabbix integration for Grafana OnCall canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-zabbix/" weight: 500 --- -# Connect Zabbix to Grafana OnCall +# Zabbix integration for Grafana OnCall Zabbix is an open-source monitoring software tool for diverse IT components, including networks, servers, virtual machines, and cloud services. Zabbix provides monitoring for metrics such as network utilization, CPU load, and disk space consumption. diff --git a/docs/sources/chat-options/_index.md b/docs/sources/integrations/chatops-integrations/_index.md similarity index 82% rename from docs/sources/chat-options/_index.md rename to docs/sources/integrations/chatops-integrations/_index.md index ed858e62..ee7de64b 100644 --- a/docs/sources/chat-options/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/chat-options/ + - /docs/oncall/latest/integrations/chatops-integrations/ keywords: - Grafana Cloud - Alerts @@ -9,12 +10,12 @@ keywords: - amixr - oncall - slack -title: Connect ChatOps to Grafana OnCall +title: Available ChatOps integrations canonical: "https://grafana.com/docs/oncall/latest/chat-options/" -weight: 900 +weight: 300 --- -# Connect ChatOps to Grafana OnCall +# Available ChatOps integrations Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../integrations/configure-outgoing-webhooks.md" >}}). diff --git a/docs/sources/chat-options/configure-slack.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md similarity index 98% rename from docs/sources/chat-options/configure-slack.md rename to docs/sources/integrations/chatops-integrations/configure-slack/index.md index 77126bb9..0b0901a1 100644 --- a/docs/sources/chat-options/configure-slack.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/chat-options/configure-slack/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/chat-options/configure-teams.md b/docs/sources/integrations/chatops-integrations/configure-teams/index.md similarity index 97% rename from docs/sources/chat-options/configure-teams.md rename to docs/sources/integrations/chatops-integrations/configure-teams/index.md index b17ca99d..7e8dc62e 100644 --- a/docs/sources/chat-options/configure-teams.md +++ b/docs/sources/integrations/chatops-integrations/configure-teams/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/chat-options/configure-teams/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/chat-options/configure-telegram.md b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md similarity index 97% rename from docs/sources/chat-options/configure-telegram.md rename to docs/sources/integrations/chatops-integrations/configure-telegram/index.md index daef6ab6..0a8796b6 100644 --- a/docs/sources/chat-options/configure-telegram.md +++ b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/chat-options/configure-telegram/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ keywords: - Grafana Cloud - Alerts From 80c572ee23742c76cd6d4e839f47415789934d72 Mon Sep 17 00:00:00 2001 From: alyssa wada Date: Wed, 12 Oct 2022 14:20:19 -0600 Subject: [PATCH 05/50] relrefs for batch 1 --- docs/sources/get-started/_index.md | 4 ++-- docs/sources/integrations/available-integrations/_index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index e84ffdd2..183b242d 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -59,7 +59,7 @@ Regardless of where your alerts originate, you can send them to Grafana OnCall v 2. Explore the alert by clicking on the title of the alert. 3. Acknowledge and resolve the test alert. -For more information on Grafana OnCall integrations and further configuration guidance, refer to, [Connect to Grafana OnCall]({{< relref "../integrations" >}}) +For more information on Grafana OnCall integrations and further configuration guidance, refer to, [Grafana OnCall integrations]({{< relref "/../../integrations/_index.md" >}}) ### Configure Escalation Chains @@ -105,7 +105,7 @@ To configure Slack for Grafana OnCall: 5. Click Allow to allow Grafana OnCall to access Slack. 6. Ensure users verify their Slack accounts in their user profile in Grafana OnCall. -For further instruction on connecting to your Slack workspace, refer to [Connect Slack to Grafana OnCall]({{< relref "../chat-options/configure-slack" >}}) +For further instruction on connecting to your Slack workspace, refer to [Slack integration for Grafana OnCall]({{< relref "/../../integrations/chatops-integrations/configure-slack/index.md" >}}) ### Add your on-call schedule diff --git a/docs/sources/integrations/available-integrations/_index.md b/docs/sources/integrations/available-integrations/_index.md index 0f2c0b97..d333bcbb 100644 --- a/docs/sources/integrations/available-integrations/_index.md +++ b/docs/sources/integrations/available-integrations/_index.md @@ -19,7 +19,7 @@ weight: 100 Grafana OnCall can connect directly to the monitoring services where your alerts originate. All currently available integrations are listed in the Grafana OnCall **Create Integration** section. -If the integration you're looking for isn't currently listed, see [Configure Webhook integrations for Grafana OnCall]({{< relref "../add-webhook-integration/" >}}) to integration your monitoring system with Grafana OnCall. +If the integration you're looking for isn't currently listed, see [Webhook integrations for Grafana OnCall]({{< relref "/../configure-webhook/index.md" >}}) to integration your monitoring system with Grafana OnCall. > **Note:** Some integrations are available for Grafana Cloud instances only. See individual integration guides for more information. From 7d5d205996b2d722ef896422919dc13ddc4da9a9 Mon Sep 17 00:00:00 2001 From: alyssa wada Date: Wed, 12 Oct 2022 15:40:52 -0600 Subject: [PATCH 06/50] restructure batch 2 --- docs/sources/alert-behavior/_index.md | 10 ++++++++++ .../alert-templates/index.md} | 6 ++++-- .../outgoing-webhooks/index.md} | 1 + docs/sources/escalation-policies/_index.md | 6 +++--- .../index.md} | 0 .../{configure-routes.md => configure-routes/index.md} | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 docs/sources/alert-behavior/_index.md rename docs/sources/{integrations/create-custom-templates.md => alert-behavior/alert-templates/index.md} (97%) rename docs/sources/{integrations/configure-outgoing-webhooks.md => alert-behavior/outgoing-webhooks/index.md} (97%) rename docs/sources/escalation-policies/{configure-escalation-chains.md => configure-escalation-chains/index.md} (100%) rename docs/sources/escalation-policies/{configure-routes.md => configure-routes/index.md} (99%) diff --git a/docs/sources/alert-behavior/_index.md b/docs/sources/alert-behavior/_index.md new file mode 100644 index 00000000..5706f184 --- /dev/null +++ b/docs/sources/alert-behavior/_index.md @@ -0,0 +1,10 @@ +--- +title: Configure alert behavior for Grafana OnCall +weight: 900 +canonical: "https://grafana.com/docs/oncall/latest/alert-behavior/" +aliases: + - /docs/oncall/latest/alert-behavior/ +--- + +# Configure alert behavior for Grafana OnCall + diff --git a/docs/sources/integrations/create-custom-templates.md b/docs/sources/alert-behavior/alert-templates/index.md similarity index 97% rename from docs/sources/integrations/create-custom-templates.md rename to docs/sources/alert-behavior/alert-templates/index.md index d99cce17..35861520 100644 --- a/docs/sources/integrations/create-custom-templates.md +++ b/docs/sources/alert-behavior/alert-templates/index.md @@ -1,18 +1,20 @@ --- aliases: - /docs/oncall/latest/integrations/create-custom-templates/ + - /docs/oncall/latest/alert-behavior/alert-templates/ + - /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ keywords: - Grafana Cloud - Alerts - Notifications - on-call - Jinja -title: Configure alerts in Grafana OnCall +title: Configure alert templates canonical: "https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/" weight: 300 --- -# Configure alerts in Grafana OnCall +# Configure alert templates Grafana OnCall can integrate with any monitoring systems that can send alerts using webhooks with JSON payloads. By default, webhooks deliver raw JSON payloads. When Grafana OnCall receives an alert and parses its payload, a default pre configured alert template is applied to modify the alert payload to be more human readable. These alert templates are customizable for any integration. diff --git a/docs/sources/integrations/configure-outgoing-webhooks.md b/docs/sources/alert-behavior/outgoing-webhooks/index.md similarity index 97% rename from docs/sources/integrations/configure-outgoing-webhooks.md rename to docs/sources/alert-behavior/outgoing-webhooks/index.md index bff8c74e..c02fd3a3 100644 --- a/docs/sources/integrations/configure-outgoing-webhooks.md +++ b/docs/sources/alert-behavior/outgoing-webhooks/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/integrations/configure-outgoing-webhooks/ + - /docs/oncall/latest/alert-behavior/outgoing-webhooks/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/escalation-policies/_index.md b/docs/sources/escalation-policies/_index.md index 99326a51..5582eda8 100644 --- a/docs/sources/escalation-policies/_index.md +++ b/docs/sources/escalation-policies/_index.md @@ -1,13 +1,13 @@ --- -title: Configure escalation chains and routes for Grafana OnCall +title: Escalation Chains and Routes weight: 700 canonical: "https://grafana.com/docs/oncall/latest/escalation-policies/" aliases: - - /docs/oncall/latest/chat-options/escalation-policies/ + - /docs/oncall/latest/escalation-policies/ --- -# Configure escalation chains and routes for Grafana OnCall +# Escalation Chains and Routes Escalation chains and routes for Grafana OnCall diff --git a/docs/sources/escalation-policies/configure-escalation-chains.md b/docs/sources/escalation-policies/configure-escalation-chains/index.md similarity index 100% rename from docs/sources/escalation-policies/configure-escalation-chains.md rename to docs/sources/escalation-policies/configure-escalation-chains/index.md diff --git a/docs/sources/escalation-policies/configure-routes.md b/docs/sources/escalation-policies/configure-routes/index.md similarity index 99% rename from docs/sources/escalation-policies/configure-routes.md rename to docs/sources/escalation-policies/configure-routes/index.md index becaa109..e2b1da94 100644 --- a/docs/sources/escalation-policies/configure-routes.md +++ b/docs/sources/escalation-policies/configure-routes/index.md @@ -14,7 +14,7 @@ canonical: "https://grafana.com/docs/oncall/latest/escalation-policies/configure weight: 300 --- -# Configure and manage routes +# Configure and manage Routes Set up escalation chains and routes to configure escalation behavior for alert group notifications. From 8e2111782140904e0758a715a66a9f557963d144 Mon Sep 17 00:00:00 2001 From: alyssa wada Date: Wed, 12 Oct 2022 16:14:06 -0600 Subject: [PATCH 07/50] relref fixes --- docs/sources/get-started/_index.md | 4 ++-- docs/sources/integrations/_index.md | 2 +- docs/sources/integrations/available-integrations/_index.md | 2 +- .../available-integrations/configure-webhook/index.md | 2 +- docs/sources/integrations/chatops-integrations/_index.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 183b242d..8897e0a4 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -59,7 +59,7 @@ Regardless of where your alerts originate, you can send them to Grafana OnCall v 2. Explore the alert by clicking on the title of the alert. 3. Acknowledge and resolve the test alert. -For more information on Grafana OnCall integrations and further configuration guidance, refer to, [Grafana OnCall integrations]({{< relref "/../../integrations/_index.md" >}}) +For more information on Grafana OnCall integrations and further configuration guidance, refer to, [Grafana OnCall integrations]({{< relref "../integrations" >}}) ### Configure Escalation Chains @@ -105,7 +105,7 @@ To configure Slack for Grafana OnCall: 5. Click Allow to allow Grafana OnCall to access Slack. 6. Ensure users verify their Slack accounts in their user profile in Grafana OnCall. -For further instruction on connecting to your Slack workspace, refer to [Slack integration for Grafana OnCall]({{< relref "/../../integrations/chatops-integrations/configure-slack/index.md" >}}) +For further instruction on connecting to your Slack workspace, refer to [Slack integration for Grafana OnCall]({{< relref "../chatops-integrations/configure-slack" >}}) ### Add your on-call schedule diff --git a/docs/sources/integrations/_index.md b/docs/sources/integrations/_index.md index fde6b402..f310e621 100644 --- a/docs/sources/integrations/_index.md +++ b/docs/sources/integrations/_index.md @@ -53,7 +53,7 @@ To customize alert grouping for an integration: 2. Select **Alert Behavior** from the dropdown menu next to **Edit template for**. 3. Edit the **grouping id**, **acknowledge condition**, and **resolve condition** templates as needed to customize your alert behavior. -For more information on alert templates, see [Configure alerts in Grafana OnCall]({{< relref "create-custom-templates/" >}}) +For more information on alert templates, see [Configure alerts templates]({{< relref "../alert-behavior/alert-templates" >}}) #### Add Routes diff --git a/docs/sources/integrations/available-integrations/_index.md b/docs/sources/integrations/available-integrations/_index.md index d333bcbb..cf87cf4b 100644 --- a/docs/sources/integrations/available-integrations/_index.md +++ b/docs/sources/integrations/available-integrations/_index.md @@ -19,7 +19,7 @@ weight: 100 Grafana OnCall can connect directly to the monitoring services where your alerts originate. All currently available integrations are listed in the Grafana OnCall **Create Integration** section. -If the integration you're looking for isn't currently listed, see [Webhook integrations for Grafana OnCall]({{< relref "/../configure-webhook/index.md" >}}) to integration your monitoring system with Grafana OnCall. +If the integration you're looking for isn't currently listed, see [Webhook integrations for Grafana OnCall]({{< relref "../available-integrations/configure-webhook" >}}) to integration your monitoring system with Grafana OnCall. > **Note:** Some integrations are available for Grafana Cloud instances only. See individual integration guides for more information. diff --git a/docs/sources/integrations/available-integrations/configure-webhook/index.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md index 774d7ae0..e5c2deac 100644 --- a/docs/sources/integrations/available-integrations/configure-webhook/index.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -57,4 +57,4 @@ For example: }' ``` -To learn how to use custom alert templates for formatted webhooks, see [Configure alerts in Grafana OnCall]({{< relref "../integrations/create-custom-templates/" >}}). +To learn how to use custom alert templates for formatted webhooks, see [Configure alerts templates]({{< relref "../alert-behavior/alert-templates" >}}). diff --git a/docs/sources/integrations/chatops-integrations/_index.md b/docs/sources/integrations/chatops-integrations/_index.md index ee7de64b..d4afa57c 100644 --- a/docs/sources/integrations/chatops-integrations/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -17,7 +17,7 @@ weight: 300 # Available ChatOps integrations -Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../integrations/configure-outgoing-webhooks.md" >}}). +Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../alert-behavior/outgoing-webhooks" >}}). To configure supported messaging apps, see the following topics: From 6aac3f16e8c4d5ab530f855e785fac404dc04501 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 18 Oct 2022 17:09:15 +0200 Subject: [PATCH 08/50] Telegram redesign --- .../TelegramIntegrationButton.module.css | 10 ++ .../TelegramIntegrationButton.tsx | 170 +++++++----------- .../tabs/TelegramInfo/TelegramInfo.module.css | 11 ++ .../parts/tabs/TelegramInfo/TelegramInfo.tsx | 99 ++++++---- grafana-plugin/src/icons/index.tsx | 20 +++ .../TelegramSettings.module.css | 9 + .../TelegramSettings/TelegramSettings.tsx | 94 ++++++---- 7 files changed, 244 insertions(+), 169 deletions(-) diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css index 59d60c4e..818c6064 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css @@ -19,3 +19,13 @@ .telegram-instruction-cancel { margin-top: 24px; } + +.telegram-block { + width: 100%; +} + +.field-command { + margin-top: 8px; + width: 100%; + display: inline-block; +} diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx index 4e5524ae..0a2e5f2a 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx @@ -1,12 +1,13 @@ import React, { useCallback, useState, useEffect } from 'react'; -import { Button, Modal, Icon, HorizontalGroup } from '@grafana/ui'; +import { Button, Modal, Icon, HorizontalGroup, VerticalGroup, Field, Input } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import CopyToClipboard from 'react-copy-to-clipboard'; import Text from 'components/Text/Text'; import WithConfirm from 'components/WithConfirm/WithConfirm'; +import Block from 'components/GBlock/Block'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { useStore } from 'state/useStore'; import { UserAction } from 'state/userAction'; @@ -47,7 +48,7 @@ const TelegramIntegrationButton = observer((props: TelegramIntegrationProps) => <> {showModal && } @@ -76,110 +77,77 @@ const TelegramModal = (props: TelegramModalProps) => { }, []); return ( - -
- Follow these steps to create and connect to a dedicated OnCall channel. -
- -
- - If you already have a dedicated channel to use with OnCall, you can use the following activation code:{' '} - {verificationCode} - - { - openNotification('Verification code copied'); - }} - > - - - + + + + + If you already have a private channel to work with OnCall, use the following activation code: + + + { + openNotification('Code is copied'); + }} + > + + + } + /> + + + Setup new channel. + + 1. Open Telegram, create a new Private Channel and enable{' '} + Sign Messages in settings. -
- -
- 1. Create a New Channel, and set it to Private. -
- -
- - 2. In Manage Channel, make sure Sign messages is enabled. + + 2. Create a new Discussion group. This group handles alert actions and comments.{' '} -
- -
- 3. Create a new discussion group. This group handles alert actions and comments. -
- -
- - 4. Add the discussion group to the channel. In Manage Channel, click Discussion to find and add - the new group. + + 3. Connect the discussion group with the channel. In Manage Channel, click{' '} + Discussion to find and add your group.{' '} -
- -
- - 5. Click{' '} - - {botLink} - {' '} - to add the OnCall bot to your contacts. Add the bot to your channel as an Admin. Allow it to{' '} - Post Messages. + + 4. Go to {botLink} to add the OnCall bot to your contacts. Then add the bot to your + channel as an Admin and allow it to Post Messages. -
- -
- 6. Add the bot to the discussion group. -
- -
- - 7. Send the verification code, {verificationCode} - - { - openNotification('Verification code copied'); - }} - > - - - {' '} - , to the channel and wait for the confirmation message. + 5. Add the bot to the discussion group. + + 6. Send this verification code to the channel and wait for the confirmation message: + + { + openNotification('Code is copied'); + }} + > + + + } + /> + -
- -
- 8. Make sure users connect their Telegram accounts in their OnCall user profile. -
- -
- 9. Done! Now you can manage alerts in your Telegram workspace. -
- -
- - Each alert group notification is assigned a dedicated discussion. Users can perform notification actions - (acknowledge, resolve, silence) and discuss alerts in the comments section of the discussions. - - -
- -
- - - - -
+ 7. Start to manage alerts in your team Telegram workspace. +
+ + + + +
+
); }; diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css index b5c7e681..29eec363 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css @@ -6,3 +6,14 @@ display: flex; justify-content: space-between; } + +.automatic-connect-telegram-block { + width: 100%; + display: flex; + justify-content: space-between; +} + +.field-command { + width: 100%; + display: inline-block; +} \ No newline at end of file diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx index 633c9d8b..b191f4dd 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx @@ -1,15 +1,17 @@ import React, { HTMLAttributes, useEffect, useState } from 'react'; -import { Alert, Button, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui'; +import { Alert, Button, HorizontalGroup, Icon, VerticalGroup, Field, Input } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import CopyToClipboard from 'react-copy-to-clipboard'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; +import Block from 'components/GBlock/Block'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { openNotification } from 'utils'; +import { TelegramColorIcon } from 'icons'; import styles from './TelegramInfo.module.css'; @@ -37,45 +39,66 @@ const TelegramInfo = observer((props: TelegramInfoProps) => { <> {telegramConfigured || !store.hasFeature(AppFeature.LiveSettings) ? ( - - - - Or add bot manually: - - - 1) Go to{' '} - - {botLink} - - - - - 2) Send - {verificationCode} - { - openNotification('Verification code copied'); - }} - > - - - to telegram bot - + Connect personal Telegram + + Connect Telegram automatically + + + + + Manual connection: + + + 1. Go to{' '} + + {botLink} + + + + + 2. Send this verification code to the bot and wait for the confirmation message: + + + { + openNotification('Code is copied'); + }} + > + + + } + /> + + 3. Refresh the page and start to manage alerts in your personal Telegram. ) : ( - - Can't connect Telegram. Check ENV variables{' '} - related to Telegram. - - } - /> + + Connect Telegram workspace + + + + You can manage incidents in your team Telegram channel or from personal direct messages. + + + To connect channel setup Telegram environment first, which includes connection to your bot and host URL. + + + More details in{' '} + + our documentation + + + + + + + + )} ); diff --git a/grafana-plugin/src/icons/index.tsx b/grafana-plugin/src/icons/index.tsx index ff0afb08..aca2e3d3 100644 --- a/grafana-plugin/src/icons/index.tsx +++ b/grafana-plugin/src/icons/index.tsx @@ -269,3 +269,23 @@ export const IsOncallIcon = (props: IsOncallIconProps) => { ); }; + +export const TelegramColorIcon = () => { + return ( + + + + + + + ); +}; diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css index 837f409b..b84ff65e 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css @@ -6,3 +6,12 @@ display: flex; justify-content: space-between; } + +.telegram-infoblock { + text-align: center; + width: 725px; +} + +ul { + margin: 20px 30px; +} \ No newline at end of file diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx index d8b694c6..37eb8165 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx @@ -1,12 +1,13 @@ import React, { Component } from 'react'; -import { Alert, Button, HorizontalGroup, Icon, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; +import { Alert, Badge, Button, HorizontalGroup, Icon, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import GTable from 'components/GTable/GTable'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; +import Block from 'components/GBlock/Block'; import Tutorial from 'components/Tutorial/Tutorial'; import { TutorialStep } from 'components/Tutorial/Tutorial.types'; import WithConfirm from 'components/WithConfirm/WithConfirm'; @@ -15,6 +16,7 @@ import { TelegramChannel } from 'models/telegram_channel/telegram_channel.types' import { AppFeature } from 'state/features'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; +import { TelegramColorIcon } from 'icons'; import styles from './TelegramSettings.module.css'; @@ -47,16 +49,28 @@ class TelegramSettings extends Component { if (!telegramConfigured && store.hasFeature(AppFeature.LiveSettings)) { return ( - - Can't connect Telegram. Check ENV variables{' '} - related to Telegram. - - } - /> + + Connect Telegram workspace + + + + You can manage incidents in your team Telegram channel or from personal direct messages. + + + To connect channel setup Telegram environment first, which includes connection to your bot and host URL. + + + More details in{' '} + + our documentation + + + + + + + + ); } @@ -66,38 +80,51 @@ class TelegramSettings extends Component { if (!connectedChannels.length) { return ( - + Connect Telegram workspace + + + You can manage incidents in your team Telegram channel or from personal direct messages. - Bring the whole incident lifecycle into your chat workspace. Everything from alerts, monitoring, and - escalations to reports. + More details in{' '} + + our documentation + - - } - /> + + + Features +
    +
  • perform actions (acknowledge, resolve, silence)
  • +
  • discuss alerts in comments
  • +
  • notifications to users accounts will be served as links to the main channel
  • +
+ Make sure your team connects Telegram in their OnCall user profiles too or they cannot manage incidents. +
+ + + + + + + ); } const columns = [ { - width: '30%', - title: 'Channel name', - dataIndex: 'channel_name', + width: '35%', + title: 'Channel', + key: 'name', + render: this.renderChannelName, }, { - width: '30%', - title: 'Discussion group name', + width: '35%', + title: 'Discussion group', dataIndex: 'discussion_group_name', }, - { - width: '10%', - title: 'Is default channel', - dataIndex: 'is_default_channel', - render: this.renderDefaultChannel, - }, { width: '30%', key: 'action', @@ -126,6 +153,13 @@ class TelegramSettings extends Component { ); } + renderChannelName = (record: TelegramChannel) => { + return ( + <> + {record.channel_name} {record.is_default_channel && } + + ); + }; renderDefaultChannel = (isDefault: boolean) => { return <>{isDefault && }; }; From e5513d1275806b1a35edfe93f85868a6d10faf95 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Tue, 18 Oct 2022 14:41:22 -0400 Subject: [PATCH 09/50] Fix relref links Signed-off-by: Jack Baldry --- docs/sources/get-started/_index.md | 2 +- .../available-integrations/configure-webhook/index.md | 2 +- docs/sources/integrations/chatops-integrations/_index.md | 2 +- .../integrations/chatops-integrations/configure-slack/index.md | 2 +- docs/sources/oncall-api-reference/integrations.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 8897e0a4..7cb44aaf 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -105,7 +105,7 @@ To configure Slack for Grafana OnCall: 5. Click Allow to allow Grafana OnCall to access Slack. 6. Ensure users verify their Slack accounts in their user profile in Grafana OnCall. -For further instruction on connecting to your Slack workspace, refer to [Slack integration for Grafana OnCall]({{< relref "../chatops-integrations/configure-slack" >}}) +For further instruction on connecting to your Slack workspace, refer to [Slack integration for Grafana OnCall]({{< relref "../integrations/chatops-integrations/configure-slack/" >}}) ### Add your on-call schedule diff --git a/docs/sources/integrations/available-integrations/configure-webhook/index.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md index e5c2deac..4ba1ef8a 100644 --- a/docs/sources/integrations/available-integrations/configure-webhook/index.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -57,4 +57,4 @@ For example: }' ``` -To learn how to use custom alert templates for formatted webhooks, see [Configure alerts templates]({{< relref "../alert-behavior/alert-templates" >}}). +To learn how to use custom alert templates for formatted webhooks, see [Configure alerts templates]({{< relref "../../../alert-behavior/alert-templates/" >}}). diff --git a/docs/sources/integrations/chatops-integrations/_index.md b/docs/sources/integrations/chatops-integrations/_index.md index d4afa57c..3dbef161 100644 --- a/docs/sources/integrations/chatops-integrations/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -17,7 +17,7 @@ weight: 300 # Available ChatOps integrations -Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../alert-behavior/outgoing-webhooks" >}}). +Grafana OnCall directly supports the export of alert notifications to some popular messaging applications like Slack and Telegram. You can use outgoing webhooks to applications that aren't directly supported. For information on configuring outgoing webhooks, see [Send alert group notifications by webhook]({{< relref "../../alert-behavior/outgoing-webhooks/" >}}). To configure supported messaging apps, see the following topics: diff --git a/docs/sources/integrations/chatops-integrations/configure-slack/index.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md index 0b0901a1..ffc0596e 100644 --- a/docs/sources/integrations/chatops-integrations/configure-slack/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -26,7 +26,7 @@ Integrating your Slack workspace with Grafana OnCall allows users and teams to b To install the Slack integration, you must have Admin permissions in your Grafana instance as well as the Slack workspace that you’d like to integrate with. -For Open Source Grafana OnCall Slack installation guidance, refer to [Open Source Grafana OnCall]({{< relref "../open-source" >}}). +For Open Source Grafana OnCall Slack installation guidance, refer to [Open Source Grafana OnCall]({{< relref "../../../open-source/" >}}). ## Install Slack integration for Grafana OnCall diff --git a/docs/sources/oncall-api-reference/integrations.md b/docs/sources/oncall-api-reference/integrations.md index 19e3edee..3ad4c725 100644 --- a/docs/sources/oncall-api-reference/integrations.md +++ b/docs/sources/oncall-api-reference/integrations.md @@ -68,7 +68,7 @@ The above command returns JSON structured in the following way: ``` Integrations are sources of alerts and alert groups for Grafana OnCall. -For example, to learn how to integrate Grafana OnCall with Alertmanager see [Alertmanager]({{< relref "../integrations/available-integrations/add-alertmanager/" >}}). +For example, to learn how to integrate Grafana OnCall with Alertmanager see [Alertmanager]({{< relref "../integrations/available-integrations/configure-alertmanager/" >}}). **HTTP request** From 68afc87a0ecaf4ff9805286326eb40b723d9a648 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Fri, 21 Oct 2022 14:59:21 +0200 Subject: [PATCH 10/50] small UX changes. SLACK and TELEGRAM MERGED in this branch --- .../src/components/GBlock/Block.module.css | 4 +-- .../src/components/Text/Text.module.scss | 1 - .../TelegramIntegrationButton.module.css | 5 ++++ .../TelegramIntegrationButton.tsx | 20 ++++++------- .../tabs/TelegramInfo/TelegramInfo.module.css | 1 + .../parts/tabs/TelegramInfo/TelegramInfo.tsx | 2 +- .../SlackSettings/SlackSettings.module.css | 12 +++++++- .../tabs/SlackSettings/SlackSettings.tsx | 21 +++++++------- .../TelegramSettings.module.css | 10 +++++++ .../TelegramSettings/TelegramSettings.tsx | 28 ++++++++++++------- 10 files changed, 67 insertions(+), 37 deletions(-) diff --git a/grafana-plugin/src/components/GBlock/Block.module.css b/grafana-plugin/src/components/GBlock/Block.module.css index cb56c069..49f4ed2d 100644 --- a/grafana-plugin/src/components/GBlock/Block.module.css +++ b/grafana-plugin/src/components/GBlock/Block.module.css @@ -4,11 +4,11 @@ } :global(.theme-dark) .root_bordered { - border: var(--border); + border: var(--border-weak); } :global(.theme-light) .root_bordered { - border: var(--border); + border: var(--border-weak); } :global(.theme-dark) .root_shadowed { diff --git a/grafana-plugin/src/components/Text/Text.module.scss b/grafana-plugin/src/components/Text/Text.module.scss index 411ffd33..a2ea3832 100644 --- a/grafana-plugin/src/components/Text/Text.module.scss +++ b/grafana-plugin/src/components/Text/Text.module.scss @@ -17,7 +17,6 @@ } &--link { color: var(--primary-text-link); - text-decoration: underline; } &--success { color: var(--green-5); diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css index 818c6064..a9a45ed3 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css @@ -29,3 +29,8 @@ width: 100%; display: inline-block; } + +.infoblock-text { + margin-left: 48px; + margin-right: 48px; +} \ No newline at end of file diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx index 0a2e5f2a..dc539d95 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx @@ -100,7 +100,7 @@ const TelegramModal = (props: TelegramModalProps) => { /> - Setup new channel. + Setup new channel 1. Open Telegram, create a new Private Channel and enable{' '} Sign Messages in settings. @@ -137,16 +137,14 @@ const TelegramModal = (props: TelegramModalProps) => { 7. Start to manage alerts in your team Telegram workspace. -
- - - - -
+ + + + ); diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css index 29eec363..6f0a61b7 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css @@ -11,6 +11,7 @@ width: 100%; display: flex; justify-content: space-between; + margin-bottom: 24px; } .field-command { diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx index b191f4dd..52bc5fe4 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx @@ -46,7 +46,7 @@ const TelegramInfo = observer((props: TelegramInfoProps) => { - Manual connection: + Manual connection 1. Go to{' '} 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 fe7c1bef..49111eb0 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 @@ -28,4 +28,14 @@ .team_workspace { height: 30px; -} \ No newline at end of file +} + +.infoblock-text { + margin-left: 48px; + margin-right: 48px; + margin-top: 24px; +} + +.infoblock-icon { + margin-top: 24px; +} 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 b372c33e..b03905bd 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 @@ -63,17 +63,12 @@ class SlackSettings extends Component { ); 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 }); } }; @@ -230,22 +225,26 @@ class SlackSettings extends Component { Connect Slack workspace - - - + +
+ +
+ 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 +
+ + After a basic workspace connection your team members need to connect their personal Slack accounts in order to be allowed to manage incidents. {isLiveSettingAvailable && ( - + For bot creating instructions and additional information please read{' '} our documentation )} + { Connect Telegram workspace - - - You can manage incidents in your team Telegram channel or from personal direct messages. + +
+ +
+ + You can manage incidents in your team Telegram channel or from personal direct messages.{' '} + - + To connect channel setup Telegram environment first, which includes connection to your bot and host URL. - + More details in{' '} our documentation @@ -83,10 +87,14 @@ class TelegramSettings extends Component { Connect Telegram workspace - - - You can manage incidents in your team Telegram channel or from personal direct messages. - + +
+ +
+ + You can manage incidents in your team Telegram channel or from personal direct messages.{' '} + + More details in{' '}
our documentation @@ -95,7 +103,7 @@ class TelegramSettings extends Component {
- Features + Features
  • perform actions (acknowledge, resolve, silence)
  • discuss alerts in comments
  • From 037fa48b8f4f7e63d86b5201e3eac117508effc4 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Tue, 25 Oct 2022 10:39:05 -0400 Subject: [PATCH 11/50] Rewrite aliases to be relative Signed-off-by: Jack Baldry --- .../alert-behavior/alert-templates/index.md | 18 +++++++-------- .../alert-behavior/outgoing-webhooks/index.md | 18 +++++++-------- docs/sources/get-started/_index.md | 12 +++++----- .../configure-alertmanager/index.md | 20 ++++++++--------- .../configure-grafana-alerting/index.md | 18 +++++++-------- .../configure-webhook/index.md | 18 +++++++-------- .../configure-zabbix/index.md | 16 +++++++------- .../chatops-integrations/_index.md | 20 ++++++++--------- .../configure-slack/index.md | 20 ++++++++--------- .../configure-teams/index.md | 22 +++++++++---------- .../configure-telegram/index.md | 20 ++++++++--------- 11 files changed, 101 insertions(+), 101 deletions(-) diff --git a/docs/sources/alert-behavior/alert-templates/index.md b/docs/sources/alert-behavior/alert-templates/index.md index 35861520..ad1da511 100644 --- a/docs/sources/alert-behavior/alert-templates/index.md +++ b/docs/sources/alert-behavior/alert-templates/index.md @@ -1,16 +1,16 @@ --- aliases: - - /docs/oncall/latest/integrations/create-custom-templates/ - - /docs/oncall/latest/alert-behavior/alert-templates/ - - /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ +- ../integrations/create-custom-templates/ +- /docs/oncall/latest/alert-behavior/alert-templates/ +- /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ +canonical: https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - Jinja +- Grafana Cloud +- Alerts +- Notifications +- on-call +- Jinja title: Configure alert templates -canonical: "https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/" weight: 300 --- diff --git a/docs/sources/alert-behavior/outgoing-webhooks/index.md b/docs/sources/alert-behavior/outgoing-webhooks/index.md index c02fd3a3..5d4eaaca 100644 --- a/docs/sources/alert-behavior/outgoing-webhooks/index.md +++ b/docs/sources/alert-behavior/outgoing-webhooks/index.md @@ -1,16 +1,16 @@ --- aliases: - - /docs/oncall/latest/integrations/configure-outgoing-webhooks/ - - /docs/oncall/latest/alert-behavior/outgoing-webhooks/ +- ../integrations/configure-outgoing-webhooks/ +- /docs/oncall/latest/alert-behavior/outgoing-webhooks/ +canonical: https://grafana.com/docs/oncall/latest/integrations/configure-outgoing-webhooks/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - amixr - - webhooks +- Grafana Cloud +- Alerts +- Notifications +- on-call +- amixr +- webhooks title: Configure outgoing webhooks for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/configure-outgoing-webhooks/" weight: 500 --- diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 7cb44aaf..64320e77 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -1,13 +1,13 @@ --- aliases: - - /docs/grafana-cloud/oncall/getting-started/ - - /docs/oncall/latest/getting-started/ +- /docs/grafana-cloud/oncall/getting-started/ +- getting-started/ +canonical: https://grafana.com/docs/oncall/latest/getting-started/ keywords: - - Get started - - On call - - Grafana Cloud +- Get started +- On call +- Grafana Cloud title: Get started with Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/getting-started/" weight: 300 --- diff --git a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md index 5787ecc0..b977af2b 100644 --- a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md +++ b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md @@ -1,17 +1,17 @@ --- aliases: - - /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ - - /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ - - /docs/oncall/latest/integrations/available-integrations/add-alertmanager/ +- /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ +- /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ +- add-alertmanager/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - Alertmanager - - Prometheus +- Grafana Cloud +- Alerts +- Notifications +- on-call +- Alertmanager +- Prometheus title: AlertManager integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/" weight: 300 --- diff --git a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md index 4b18f530..7ffd4cb6 100644 --- a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md +++ b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md @@ -1,16 +1,16 @@ --- aliases: - - /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ - - /docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ - - /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ +- /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ +- add-grafana-alerting/ +- /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - Prometheus +- Grafana Cloud +- Alerts +- Notifications +- on-call +- Prometheus title: Grafana Alerting integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/" weight: 100 --- diff --git a/docs/sources/integrations/available-integrations/configure-webhook/index.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md index 4ba1ef8a..c70b7df2 100644 --- a/docs/sources/integrations/available-integrations/configure-webhook/index.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -1,16 +1,16 @@ --- aliases: - - /docs/oncall/latest/integrations/add-webhook-integration/ - - /docs/oncall/latest/integrations/available-integrations/configure-webhook/ +- ../add-webhook-integration/ +- /docs/oncall/latest/integrations/available-integrations/configure-webhook/ +canonical: https://grafana.com/docs/oncall/latest/integrations/add-webhook-integration/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - Alertmanager - - Prometheus +- Grafana Cloud +- Alerts +- Notifications +- on-call +- Alertmanager +- Prometheus title: Webhook integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/add-webhook-integration/" weight: 700 --- diff --git a/docs/sources/integrations/available-integrations/configure-zabbix/index.md b/docs/sources/integrations/available-integrations/configure-zabbix/index.md index cc03ca78..c3e8e47b 100644 --- a/docs/sources/integrations/available-integrations/configure-zabbix/index.md +++ b/docs/sources/integrations/available-integrations/configure-zabbix/index.md @@ -1,15 +1,15 @@ --- aliases: - - /docs/oncall/latest/integrations/available-integrations/add-zabbix/ - - /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ +- add-zabbix/ +- /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-zabbix/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - Zabbix +- Grafana Cloud +- Alerts +- Notifications +- on-call +- Zabbix title: Zabbix integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-zabbix/" weight: 500 --- diff --git a/docs/sources/integrations/chatops-integrations/_index.md b/docs/sources/integrations/chatops-integrations/_index.md index 3dbef161..092425d0 100644 --- a/docs/sources/integrations/chatops-integrations/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -1,17 +1,17 @@ --- aliases: - - /docs/oncall/latest/chat-options/ - - /docs/oncall/latest/integrations/chatops-integrations/ +- ../chat-options/ +- /docs/oncall/latest/integrations/chatops-integrations/ +canonical: https://grafana.com/docs/oncall/latest/chat-options/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - amixr - - oncall - - slack +- Grafana Cloud +- Alerts +- Notifications +- on-call +- amixr +- oncall +- slack title: Available ChatOps integrations -canonical: "https://grafana.com/docs/oncall/latest/chat-options/" weight: 300 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-slack/index.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md index ffc0596e..e3ffdf0a 100644 --- a/docs/sources/integrations/chatops-integrations/configure-slack/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -1,17 +1,17 @@ --- aliases: - - /docs/oncall/latest/chat-options/configure-slack/ - - /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ +- ../../chat-options/configure-slack/ +- /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ +canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-slack/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - amixr - - oncall - - slack +- Grafana Cloud +- Alerts +- Notifications +- on-call +- amixr +- oncall +- slack title: Slack integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/chat-options/configure-slack/" weight: 100 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-teams/index.md b/docs/sources/integrations/chatops-integrations/configure-teams/index.md index 7e8dc62e..5aaff91b 100644 --- a/docs/sources/integrations/chatops-integrations/configure-teams/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-teams/index.md @@ -1,18 +1,18 @@ --- aliases: - - /docs/oncall/latest/chat-options/configure-teams/ - - /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ +- ../../chat-options/configure-teams/ +- /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ +canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-teams/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - amixr - - oncall - - MS Team - - Microsoft +- Grafana Cloud +- Alerts +- Notifications +- on-call +- amixr +- oncall +- MS Team +- Microsoft title: Microsoft Teams integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/chat-options/configure-teams/" weight: 500 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md index 0a8796b6..b1e38b7b 100644 --- a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md @@ -1,17 +1,17 @@ --- aliases: - - /docs/oncall/latest/chat-options/configure-telegram/ - - /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ +- ../../chat-options/configure-telegram/ +- /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ +canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-telegram/ keywords: - - Grafana Cloud - - Alerts - - Notifications - - on-call - - amixr - - oncall - - telegram +- Grafana Cloud +- Alerts +- Notifications +- on-call +- amixr +- oncall +- telegram title: Telegram integration for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/chat-options/configure-telegram/" weight: 300 --- From f9b9096fa066315d39bcb78dbe4834b9939a17b8 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Tue, 25 Oct 2022 10:41:01 -0400 Subject: [PATCH 12/50] Run prettier on modified files front matter Signed-off-by: Jack Baldry --- .../alert-behavior/alert-templates/index.md | 16 +++++++-------- .../alert-behavior/outgoing-webhooks/index.md | 16 +++++++-------- docs/sources/get-started/_index.md | 10 +++++----- .../configure-alertmanager/index.md | 18 ++++++++--------- .../configure-grafana-alerting/index.md | 16 +++++++-------- .../configure-webhook/index.md | 16 +++++++-------- .../configure-zabbix/index.md | 14 ++++++------- .../chatops-integrations/_index.md | 18 ++++++++--------- .../configure-slack/index.md | 18 ++++++++--------- .../configure-teams/index.md | 20 +++++++++---------- .../configure-telegram/index.md | 18 ++++++++--------- 11 files changed, 90 insertions(+), 90 deletions(-) diff --git a/docs/sources/alert-behavior/alert-templates/index.md b/docs/sources/alert-behavior/alert-templates/index.md index ad1da511..ceb0b1e3 100644 --- a/docs/sources/alert-behavior/alert-templates/index.md +++ b/docs/sources/alert-behavior/alert-templates/index.md @@ -1,15 +1,15 @@ --- aliases: -- ../integrations/create-custom-templates/ -- /docs/oncall/latest/alert-behavior/alert-templates/ -- /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ + - ../integrations/create-custom-templates/ + - /docs/oncall/latest/alert-behavior/alert-templates/ + - /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ canonical: https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- Jinja + - Grafana Cloud + - Alerts + - Notifications + - on-call + - Jinja title: Configure alert templates weight: 300 --- diff --git a/docs/sources/alert-behavior/outgoing-webhooks/index.md b/docs/sources/alert-behavior/outgoing-webhooks/index.md index 5d4eaaca..98629cce 100644 --- a/docs/sources/alert-behavior/outgoing-webhooks/index.md +++ b/docs/sources/alert-behavior/outgoing-webhooks/index.md @@ -1,15 +1,15 @@ --- aliases: -- ../integrations/configure-outgoing-webhooks/ -- /docs/oncall/latest/alert-behavior/outgoing-webhooks/ + - ../integrations/configure-outgoing-webhooks/ + - /docs/oncall/latest/alert-behavior/outgoing-webhooks/ canonical: https://grafana.com/docs/oncall/latest/integrations/configure-outgoing-webhooks/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- amixr -- webhooks + - Grafana Cloud + - Alerts + - Notifications + - on-call + - amixr + - webhooks title: Configure outgoing webhooks for Grafana OnCall weight: 500 --- diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 64320e77..edd752ed 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -1,12 +1,12 @@ --- aliases: -- /docs/grafana-cloud/oncall/getting-started/ -- getting-started/ + - /docs/grafana-cloud/oncall/getting-started/ + - getting-started/ canonical: https://grafana.com/docs/oncall/latest/getting-started/ keywords: -- Get started -- On call -- Grafana Cloud + - Get started + - On call + - Grafana Cloud title: Get started with Grafana OnCall weight: 300 --- diff --git a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md index b977af2b..ff111678 100644 --- a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md +++ b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md @@ -1,16 +1,16 @@ --- aliases: -- /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ -- /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ -- add-alertmanager/ + - /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ + - /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ + - add-alertmanager/ canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- Alertmanager -- Prometheus + - Grafana Cloud + - Alerts + - Notifications + - on-call + - Alertmanager + - Prometheus title: AlertManager integration for Grafana OnCall weight: 300 --- diff --git a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md index 7ffd4cb6..8828a761 100644 --- a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md +++ b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md @@ -1,15 +1,15 @@ --- aliases: -- /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ -- add-grafana-alerting/ -- /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ + - /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ + - add-grafana-alerting/ + - /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- Prometheus + - Grafana Cloud + - Alerts + - Notifications + - on-call + - Prometheus title: Grafana Alerting integration for Grafana OnCall weight: 100 --- diff --git a/docs/sources/integrations/available-integrations/configure-webhook/index.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md index c70b7df2..0958279b 100644 --- a/docs/sources/integrations/available-integrations/configure-webhook/index.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -1,15 +1,15 @@ --- aliases: -- ../add-webhook-integration/ -- /docs/oncall/latest/integrations/available-integrations/configure-webhook/ + - ../add-webhook-integration/ + - /docs/oncall/latest/integrations/available-integrations/configure-webhook/ canonical: https://grafana.com/docs/oncall/latest/integrations/add-webhook-integration/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- Alertmanager -- Prometheus + - Grafana Cloud + - Alerts + - Notifications + - on-call + - Alertmanager + - Prometheus title: Webhook integration for Grafana OnCall weight: 700 --- diff --git a/docs/sources/integrations/available-integrations/configure-zabbix/index.md b/docs/sources/integrations/available-integrations/configure-zabbix/index.md index c3e8e47b..f0110edc 100644 --- a/docs/sources/integrations/available-integrations/configure-zabbix/index.md +++ b/docs/sources/integrations/available-integrations/configure-zabbix/index.md @@ -1,14 +1,14 @@ --- aliases: -- add-zabbix/ -- /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ + - add-zabbix/ + - /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-zabbix/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- Zabbix + - Grafana Cloud + - Alerts + - Notifications + - on-call + - Zabbix title: Zabbix integration for Grafana OnCall weight: 500 --- diff --git a/docs/sources/integrations/chatops-integrations/_index.md b/docs/sources/integrations/chatops-integrations/_index.md index 092425d0..b8d1fd76 100644 --- a/docs/sources/integrations/chatops-integrations/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -1,16 +1,16 @@ --- aliases: -- ../chat-options/ -- /docs/oncall/latest/integrations/chatops-integrations/ + - ../chat-options/ + - /docs/oncall/latest/integrations/chatops-integrations/ canonical: https://grafana.com/docs/oncall/latest/chat-options/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- amixr -- oncall -- slack + - Grafana Cloud + - Alerts + - Notifications + - on-call + - amixr + - oncall + - slack title: Available ChatOps integrations weight: 300 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-slack/index.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md index e3ffdf0a..57dfe60c 100644 --- a/docs/sources/integrations/chatops-integrations/configure-slack/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -1,16 +1,16 @@ --- aliases: -- ../../chat-options/configure-slack/ -- /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ + - ../../chat-options/configure-slack/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-slack/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- amixr -- oncall -- slack + - Grafana Cloud + - Alerts + - Notifications + - on-call + - amixr + - oncall + - slack title: Slack integration for Grafana OnCall weight: 100 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-teams/index.md b/docs/sources/integrations/chatops-integrations/configure-teams/index.md index 5aaff91b..76178c87 100644 --- a/docs/sources/integrations/chatops-integrations/configure-teams/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-teams/index.md @@ -1,17 +1,17 @@ --- aliases: -- ../../chat-options/configure-teams/ -- /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ + - ../../chat-options/configure-teams/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-teams/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- amixr -- oncall -- MS Team -- Microsoft + - Grafana Cloud + - Alerts + - Notifications + - on-call + - amixr + - oncall + - MS Team + - Microsoft title: Microsoft Teams integration for Grafana OnCall weight: 500 --- diff --git a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md index b1e38b7b..0720e56b 100644 --- a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md @@ -1,16 +1,16 @@ --- aliases: -- ../../chat-options/configure-telegram/ -- /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ + - ../../chat-options/configure-telegram/ + - /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-telegram/ keywords: -- Grafana Cloud -- Alerts -- Notifications -- on-call -- amixr -- oncall -- telegram + - Grafana Cloud + - Alerts + - Notifications + - on-call + - amixr + - oncall + - telegram title: Telegram integration for Grafana OnCall weight: 300 --- From f2a780c4fa20ca7b38733b711b70cac3215044f5 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Wed, 26 Oct 2022 14:13:26 +0200 Subject: [PATCH 13/50] Changes after review and change incident word --- .../SlackInstructions/SlackInstructions.tsx | 2 +- .../parts/tabs/SlackTab/SlackTab.tsx | 2 +- .../parts/tabs/TelegramInfo/TelegramInfo.tsx | 2 +- .../tabs/SlackSettings/SlackSettings.tsx | 24 ++++++++++--------- .../TelegramSettings/TelegramSettings.tsx | 6 ++--- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx index 9b4638f1..e4465c0e 100644 --- a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx @@ -23,7 +23,7 @@ const SlackInstructions: FC = observer((props) => { - You can manage incidents in your Slack workspace. + You can manage alert groups 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{' '} 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 27d44d37..e64d750d 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx @@ -25,7 +25,7 @@ export const SlackTab = () => { - Personal Slack connection will allow you to manage incidents in your connected team Internal Slack + Personal Slack connection will allow you to manage alert grouops in your connected team Internal Slack workspace. To setup personal Slack click the button below, choose workspace and click Allow. diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx index 52bc5fe4..b06f8418 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx @@ -82,7 +82,7 @@ const TelegramInfo = observer((props: TelegramInfoProps) => { - You can manage incidents in your team Telegram channel or from personal direct messages. + You can manage alert groups in your team Telegram channel or from personal direct messages. To connect channel setup Telegram environment first, which includes connection to your bot and host URL. 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 b03905bd..92fac740 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 @@ -36,8 +36,9 @@ class SlackSettings extends Component { }; componentDidMount() { - this.getSlackLiveSettings(); - this.update(); + this.getSlackLiveSettings().then(() => { + this.update(); + }); } handleOpenSlackInstructions = () => { @@ -54,14 +55,14 @@ class SlackSettings extends Component { 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 results = await store.globalSettingStore.getAll(); + + const slackClientOAUTH = results.find((element: { name: string }) => element.name === 'SLACK_CLIENT_OAUTH_ID'); + const slackClientOAUTHSecret = results.find( + (element: { name: string }) => element.name === 'SLACK_CLIENT_OAUTH_SECRET' ); - const slackRedirectHost = await store.globalSettingStore.getGlobalSettingItemByName( - 'SLACK_INSTALL_RETURN_REDIRECT_HOST' - ); - const slackSigningSecret = await store.globalSettingStore.getGlobalSettingItemByName('SLACK_SIGNING_SECRET'); + const slackRedirectHost = results.find((element: { name: string }) => element.name === 'SLACK_CLIENT_OAUTH_ID'); + const slackSigningSecret = results.find((element: { name: string }) => element.name === 'SLACK_SIGNING_SECRET'); if ( slackClientOAUTH?.error || @@ -221,6 +222,7 @@ class SlackSettings extends Component { const { store } = this.props; const { showENVVariablesButton } = this.state; const isLiveSettingAvailable = store.hasFeature(AppFeature.LiveSettings) && showENVVariablesButton; + return ( Connect Slack workspace @@ -230,11 +232,11 @@ class SlackSettings extends Component {
- Slack connection will allow you to manage incidents in your team Slack workspace. + Slack connection will allow you to manage alert groups 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. + order to be allowed to manage alert groups. {isLiveSettingAvailable && ( diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx index 1bb0ce37..d4215b35 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx @@ -57,7 +57,7 @@ class TelegramSettings extends Component {
- You can manage incidents in your team Telegram channel or from personal direct messages.{' '} + You can manage alert groups in your team Telegram channel or from personal direct messages.{' '} @@ -92,7 +92,7 @@ class TelegramSettings extends Component { - You can manage incidents in your team Telegram channel or from personal direct messages.{' '} + You can manage alert groups in your team Telegram channel or from personal direct messages.{' '} More details in{' '} @@ -109,7 +109,7 @@ class TelegramSettings extends Component {
  • discuss alerts in comments
  • notifications to users accounts will be served as links to the main channel
  • - Make sure your team connects Telegram in their OnCall user profiles too or they cannot manage incidents. + Make sure your team connects Telegram in their OnCall user profiles too or they cannot manage alert groups.
    From 9d82521a749aea79641288727d94cd475ae23bd6 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Wed, 26 Oct 2022 11:49:19 -0400 Subject: [PATCH 14/50] Build grafana-cloud docs with oncall content locally Signed-off-by: Jack Baldry --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index e66f1c1c..3762a58d 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -8,4 +8,4 @@ pull: .PHONY: docs docs: pull - docker run -v '$(shell pwd)/sources:$(CONTENT_PATH):Z' -p $(PORT) --rm -it $(IMAGE) + docker run -v '$(shell pwd)/sources:$(CONTENT_PATH):Z' -v '$(shell pwd)/sources:/jugo/content/docs/grafana-cloud/oncall:Z' -p $(PORT) --rm -it $(IMAGE) From 7a22410bf61972ed0d8650a145bc5735bd71ba00 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Wed, 26 Oct 2022 12:21:59 -0400 Subject: [PATCH 15/50] Remove grafana-cloud absolute aliases They will only redirect to oncall docs and not to grafana-cloud docs. Relative aliases will correctly redirect within the grafana-cloud docs. Signed-off-by: Jack Baldry --- docs/sources/_index.md | 1 - docs/sources/alert-behavior/alert-templates/index.md | 1 - docs/sources/get-started/_index.md | 1 - docs/sources/integrations/_index.md | 1 - docs/sources/integrations/available-integrations/_index.md | 2 -- .../available-integrations/configure-alertmanager/index.md | 2 -- .../available-integrations/configure-grafana-alerting/index.md | 2 -- docs/sources/oncall-api-reference/alertgroups.md | 1 - docs/sources/oncall-api-reference/alerts.md | 1 - docs/sources/oncall-api-reference/escalation_chains.md | 1 - docs/sources/oncall-api-reference/escalation_policies.md | 1 - docs/sources/oncall-api-reference/integrations.md | 1 - docs/sources/oncall-api-reference/on_call_shifts.md | 1 - docs/sources/oncall-api-reference/outgoing_webhooks.md | 1 - .../sources/oncall-api-reference/personal_notification_rules.md | 1 - docs/sources/oncall-api-reference/postmortem_messages.md | 1 - docs/sources/oncall-api-reference/postmortems.md | 1 - docs/sources/oncall-api-reference/routes.md | 1 - docs/sources/oncall-api-reference/schedules.md | 1 - docs/sources/oncall-api-reference/slack_channels.md | 1 - docs/sources/oncall-api-reference/user_groups.md | 1 - docs/sources/oncall-api-reference/users.md | 1 - docs/sources/open-source/_index.md | 1 - 23 files changed, 26 deletions(-) diff --git a/docs/sources/_index.md b/docs/sources/_index.md index 29928422..c978cb37 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/ - /docs/oncall/latest/ keywords: - Grafana Cloud diff --git a/docs/sources/alert-behavior/alert-templates/index.md b/docs/sources/alert-behavior/alert-templates/index.md index ceb0b1e3..66b53d6f 100644 --- a/docs/sources/alert-behavior/alert-templates/index.md +++ b/docs/sources/alert-behavior/alert-templates/index.md @@ -2,7 +2,6 @@ aliases: - ../integrations/create-custom-templates/ - /docs/oncall/latest/alert-behavior/alert-templates/ - - /docs/grafana-cloud/oncall/alert-behavior/alert-templates/ canonical: https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/ keywords: - Grafana Cloud diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index edd752ed..06e190a7 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/getting-started/ - getting-started/ canonical: https://grafana.com/docs/oncall/latest/getting-started/ keywords: diff --git a/docs/sources/integrations/_index.md b/docs/sources/integrations/_index.md index f310e621..061e4445 100644 --- a/docs/sources/integrations/_index.md +++ b/docs/sources/integrations/_index.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/integrations/ - /docs/oncall/latest/integrations/ keywords: - Grafana Cloud diff --git a/docs/sources/integrations/available-integrations/_index.md b/docs/sources/integrations/available-integrations/_index.md index cf87cf4b..61d02ae0 100644 --- a/docs/sources/integrations/available-integrations/_index.md +++ b/docs/sources/integrations/available-integrations/_index.md @@ -1,8 +1,6 @@ --- aliases: - - /docs/grafana-cloud/oncall/integrations/add-integration/ - /docs/oncall/latest/integrations/available-integrations/ - - /docs/grafana-cloud/oncall/integrations/available-integration/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md index ff111678..1a0a6d7a 100644 --- a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md +++ b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md @@ -1,7 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/available-integrations/add-alertmanager/ - - /docs/grafana-cloud/oncall/available-integrations/configure-alertmanager/ - add-alertmanager/ canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/ keywords: diff --git a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md index 8828a761..608c4228 100644 --- a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md +++ b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md @@ -1,8 +1,6 @@ --- aliases: - - /docs/grafana-cloud/oncall/integrations/add-grafana-alerting/ - add-grafana-alerting/ - - /docs/grafana-cloud/oncall/integrations/configure-grafana-alerting/ canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ keywords: - Grafana Cloud diff --git a/docs/sources/oncall-api-reference/alertgroups.md b/docs/sources/oncall-api-reference/alertgroups.md index 8e90f6d3..76be1aca 100644 --- a/docs/sources/oncall-api-reference/alertgroups.md +++ b/docs/sources/oncall-api-reference/alertgroups.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/alertgroups/ - /docs/oncall/latest/oncall-api-reference/alertgroups/ title: Alert groups HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/alertgroups/" diff --git a/docs/sources/oncall-api-reference/alerts.md b/docs/sources/oncall-api-reference/alerts.md index 8a062581..be215ed8 100644 --- a/docs/sources/oncall-api-reference/alerts.md +++ b/docs/sources/oncall-api-reference/alerts.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/alerts/ - /docs/oncall/latest/oncall-api-reference/alerts/ title: Alerts HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/alerts/" diff --git a/docs/sources/oncall-api-reference/escalation_chains.md b/docs/sources/oncall-api-reference/escalation_chains.md index 8723b6a6..29638dbd 100644 --- a/docs/sources/oncall-api-reference/escalation_chains.md +++ b/docs/sources/oncall-api-reference/escalation_chains.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/escalation_chains/ - /docs/oncall/latest/oncall-api-reference/escalation_chains/ title: Escalation Chains HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_chains/" diff --git a/docs/sources/oncall-api-reference/escalation_policies.md b/docs/sources/oncall-api-reference/escalation_policies.md index 76d253f3..06e366fb 100644 --- a/docs/sources/oncall-api-reference/escalation_policies.md +++ b/docs/sources/oncall-api-reference/escalation_policies.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/escalation_policies/ - /docs/oncall/latest/oncall-api-reference/escalation_policies/ title: Escalation Policies HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_policies/" diff --git a/docs/sources/oncall-api-reference/integrations.md b/docs/sources/oncall-api-reference/integrations.md index 3ad4c725..0f7af01d 100644 --- a/docs/sources/oncall-api-reference/integrations.md +++ b/docs/sources/oncall-api-reference/integrations.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/integrations/ - /docs/oncall/latest/oncall-api-reference/integrations/ title: Integrations HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/integrations/" diff --git a/docs/sources/oncall-api-reference/on_call_shifts.md b/docs/sources/oncall-api-reference/on_call_shifts.md index 8f43975d..37bc19b2 100644 --- a/docs/sources/oncall-api-reference/on_call_shifts.md +++ b/docs/sources/oncall-api-reference/on_call_shifts.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/on_call_shifts/ - /docs/oncall/latest/oncall-api-reference/on_call_shifts/ title: OnCall shifts HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/on_call_shifts/" diff --git a/docs/sources/oncall-api-reference/outgoing_webhooks.md b/docs/sources/oncall-api-reference/outgoing_webhooks.md index 0e26f012..a9d7fec1 100644 --- a/docs/sources/oncall-api-reference/outgoing_webhooks.md +++ b/docs/sources/oncall-api-reference/outgoing_webhooks.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ - /docs/oncall/latest/oncall-api-reference/outgoing_webhooks/ title: Outgoing webhooks HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/outgoing_webhooks/" diff --git a/docs/sources/oncall-api-reference/personal_notification_rules.md b/docs/sources/oncall-api-reference/personal_notification_rules.md index dc6e8562..2165d1c3 100644 --- a/docs/sources/oncall-api-reference/personal_notification_rules.md +++ b/docs/sources/oncall-api-reference/personal_notification_rules.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/personal_notification_rules/ - /docs/oncall/latest/oncall-api-reference/personal_notification_rules/ title: Personal Notification Rules HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/" diff --git a/docs/sources/oncall-api-reference/postmortem_messages.md b/docs/sources/oncall-api-reference/postmortem_messages.md index b2fefa19..4046b11f 100644 --- a/docs/sources/oncall-api-reference/postmortem_messages.md +++ b/docs/sources/oncall-api-reference/postmortem_messages.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/postmortem_messages/ - /docs/oncall/latest/oncall-api-reference/postmortem_messages/ draft: true title: Postmortem Messages HTTP API diff --git a/docs/sources/oncall-api-reference/postmortems.md b/docs/sources/oncall-api-reference/postmortems.md index c98026ae..7393201d 100644 --- a/docs/sources/oncall-api-reference/postmortems.md +++ b/docs/sources/oncall-api-reference/postmortems.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/postmortems/ - /docs/oncall/latest/oncall-api-reference/postmortems/ draft: true title: Postmortem HTTP API diff --git a/docs/sources/oncall-api-reference/routes.md b/docs/sources/oncall-api-reference/routes.md index 78eb387f..bff5d3d0 100644 --- a/docs/sources/oncall-api-reference/routes.md +++ b/docs/sources/oncall-api-reference/routes.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/routes/ - /docs/oncall/latest/oncall-api-reference/routes/ title: Routes HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/routes/" diff --git a/docs/sources/oncall-api-reference/schedules.md b/docs/sources/oncall-api-reference/schedules.md index d7ec9273..c42f6bce 100644 --- a/docs/sources/oncall-api-reference/schedules.md +++ b/docs/sources/oncall-api-reference/schedules.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/schedules/ - /docs/oncall/latest/oncall-api-reference/schedules/ title: Schedule HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/schedules/" diff --git a/docs/sources/oncall-api-reference/slack_channels.md b/docs/sources/oncall-api-reference/slack_channels.md index 6c7d09c3..887db569 100644 --- a/docs/sources/oncall-api-reference/slack_channels.md +++ b/docs/sources/oncall-api-reference/slack_channels.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/slack_channels/ - /docs/oncall/latest/oncall-api-reference/slack_channels/ title: Slack Channels HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/slack_channels/" diff --git a/docs/sources/oncall-api-reference/user_groups.md b/docs/sources/oncall-api-reference/user_groups.md index e2021770..5a8815d3 100644 --- a/docs/sources/oncall-api-reference/user_groups.md +++ b/docs/sources/oncall-api-reference/user_groups.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/user_groups/ - /docs/oncall/latest/oncall-api-reference/user_groups/ title: OnCall User Groups HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/user_groups/" diff --git a/docs/sources/oncall-api-reference/users.md b/docs/sources/oncall-api-reference/users.md index ca6d01df..c601cb42 100644 --- a/docs/sources/oncall-api-reference/users.md +++ b/docs/sources/oncall-api-reference/users.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/oncall-api-reference/users/ - /docs/oncall/latest/oncall-api-reference/users/ title: Grafana OnCall Users HTTP API canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/users/" diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md index b2e6c1df..1dee4845 100644 --- a/docs/sources/open-source/_index.md +++ b/docs/sources/open-source/_index.md @@ -1,6 +1,5 @@ --- aliases: - - /docs/grafana-cloud/oncall/open-source/ - /docs/oncall/latest/open-source/ keywords: - Open Source From 87d0a50e03dde9f67b1254977b2c80ef85dc4f4c Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Wed, 26 Oct 2022 12:32:47 -0400 Subject: [PATCH 16/50] Ensure canonical URLs match the new page locations Signed-off-by: Jack Baldry --- docs/sources/_index.md | 2 +- docs/sources/alert-behavior/_index.md | 7 +++---- docs/sources/alert-behavior/alert-templates/index.md | 2 +- docs/sources/alert-behavior/outgoing-webhooks/index.md | 2 +- docs/sources/calendar-schedules/_index.md | 2 +- docs/sources/configure-user-settings/_index.md | 2 +- docs/sources/escalation-policies/_index.md | 7 +++---- .../configure-escalation-chains/index.md | 2 +- docs/sources/escalation-policies/configure-routes/index.md | 2 +- docs/sources/get-started/_index.md | 5 +++-- docs/sources/integrations/_index.md | 2 +- docs/sources/integrations/available-integrations/_index.md | 2 +- .../available-integrations/configure-alertmanager/index.md | 3 ++- .../configure-grafana-alerting/index.md | 3 ++- .../available-integrations/configure-webhook/index.md | 2 +- .../available-integrations/configure-zabbix/index.md | 2 +- docs/sources/integrations/chatops-integrations/_index.md | 2 +- .../chatops-integrations/configure-slack/index.md | 2 +- .../chatops-integrations/configure-teams/index.md | 2 +- .../chatops-integrations/configure-telegram/index.md | 2 +- docs/sources/oncall-api-reference/_index.md | 2 +- docs/sources/oncall-api-reference/alertgroups.md | 2 +- docs/sources/oncall-api-reference/alerts.md | 2 +- docs/sources/oncall-api-reference/escalation_chains.md | 2 +- docs/sources/oncall-api-reference/escalation_policies.md | 2 +- docs/sources/oncall-api-reference/integrations.md | 2 +- docs/sources/oncall-api-reference/on_call_shifts.md | 2 +- docs/sources/oncall-api-reference/outgoing_webhooks.md | 2 +- .../oncall-api-reference/personal_notification_rules.md | 2 +- docs/sources/oncall-api-reference/postmortem_messages.md | 2 +- docs/sources/oncall-api-reference/postmortems.md | 2 +- docs/sources/oncall-api-reference/routes.md | 2 +- docs/sources/oncall-api-reference/schedules.md | 2 +- docs/sources/oncall-api-reference/slack_channels.md | 2 +- docs/sources/oncall-api-reference/user_groups.md | 2 +- docs/sources/oncall-api-reference/users.md | 2 +- 36 files changed, 44 insertions(+), 43 deletions(-) diff --git a/docs/sources/_index.md b/docs/sources/_index.md index c978cb37..92f9d004 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/ +canonical: https://grafana.com/docs/oncall/latest/ keywords: - Grafana Cloud - Alerts @@ -10,7 +11,6 @@ keywords: - OnCall - irm title: Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/" weight: 1000 --- diff --git a/docs/sources/alert-behavior/_index.md b/docs/sources/alert-behavior/_index.md index 5706f184..ae8829bd 100644 --- a/docs/sources/alert-behavior/_index.md +++ b/docs/sources/alert-behavior/_index.md @@ -1,10 +1,9 @@ --- -title: Configure alert behavior for Grafana OnCall -weight: 900 -canonical: "https://grafana.com/docs/oncall/latest/alert-behavior/" aliases: - /docs/oncall/latest/alert-behavior/ +canonical: https://grafana.com/docs/oncall/latest/alert-behavior/ +title: Configure alert behavior for Grafana OnCall +weight: 900 --- # Configure alert behavior for Grafana OnCall - diff --git a/docs/sources/alert-behavior/alert-templates/index.md b/docs/sources/alert-behavior/alert-templates/index.md index 66b53d6f..5bc4321c 100644 --- a/docs/sources/alert-behavior/alert-templates/index.md +++ b/docs/sources/alert-behavior/alert-templates/index.md @@ -2,7 +2,7 @@ aliases: - ../integrations/create-custom-templates/ - /docs/oncall/latest/alert-behavior/alert-templates/ -canonical: https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/ +canonical: https://grafana.com/docs/oncall/latest/alert-behavior/alert-templates/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/alert-behavior/outgoing-webhooks/index.md b/docs/sources/alert-behavior/outgoing-webhooks/index.md index 98629cce..a88ccbf9 100644 --- a/docs/sources/alert-behavior/outgoing-webhooks/index.md +++ b/docs/sources/alert-behavior/outgoing-webhooks/index.md @@ -2,7 +2,7 @@ aliases: - ../integrations/configure-outgoing-webhooks/ - /docs/oncall/latest/alert-behavior/outgoing-webhooks/ -canonical: https://grafana.com/docs/oncall/latest/integrations/configure-outgoing-webhooks/ +canonical: https://grafana.com/docs/oncall/latest/alert-behavior/outgoing-webhooks/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/calendar-schedules/_index.md b/docs/sources/calendar-schedules/_index.md index ca3bf92b..c4838c5d 100644 --- a/docs/sources/calendar-schedules/_index.md +++ b/docs/sources/calendar-schedules/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/calendar-schedules/ +canonical: https://grafana.com/docs/oncall/latest/calendar-schedules/ description: "" keywords: - Grafana @@ -8,7 +9,6 @@ keywords: - on-call - calendar title: Configure and manage on-call schedules -canonical: "https://grafana.com/docs/oncall/latest/calendar-schedules/" weight: 1100 --- diff --git a/docs/sources/configure-user-settings/_index.md b/docs/sources/configure-user-settings/_index.md index 287adf8e..afcefb69 100644 --- a/docs/sources/configure-user-settings/_index.md +++ b/docs/sources/configure-user-settings/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/configure-user-settings/ +canonical: https://grafana.com/docs/oncall/latest/configure-user-setting/ keywords: - Grafana Cloud - Alerts @@ -10,7 +11,6 @@ keywords: - oncall - integrations title: Manage users and teams for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/configure-user-setting/" weight: 1300 --- diff --git a/docs/sources/escalation-policies/_index.md b/docs/sources/escalation-policies/_index.md index 5582eda8..ceb70476 100644 --- a/docs/sources/escalation-policies/_index.md +++ b/docs/sources/escalation-policies/_index.md @@ -1,12 +1,11 @@ --- -title: Escalation Chains and Routes -weight: 700 -canonical: "https://grafana.com/docs/oncall/latest/escalation-policies/" aliases: - /docs/oncall/latest/escalation-policies/ +canonical: https://grafana.com/docs/oncall/latest/escalation-policies/ +title: Escalation Chains and Routes +weight: 700 --- - # Escalation Chains and Routes Escalation chains and routes for Grafana OnCall diff --git a/docs/sources/escalation-policies/configure-escalation-chains/index.md b/docs/sources/escalation-policies/configure-escalation-chains/index.md index 8c244f15..9c19414b 100644 --- a/docs/sources/escalation-policies/configure-escalation-chains/index.md +++ b/docs/sources/escalation-policies/configure-escalation-chains/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/escalation-policies/configure-escalation-chains/ +canonical: https://grafana.com/docs/oncall/latest/escalation-policies/configure-escalation-chains/ keywords: - Grafana Cloud - Alerts @@ -10,7 +11,6 @@ keywords: - oncall - integrations title: Configure and manage Escalation Chains -canonical: "https://grafana.com/docs/oncall/latest/escalation-policies/configure-escalation-chains/" weight: 100 --- diff --git a/docs/sources/escalation-policies/configure-routes/index.md b/docs/sources/escalation-policies/configure-routes/index.md index e2b1da94..5de0a1a4 100644 --- a/docs/sources/escalation-policies/configure-routes/index.md +++ b/docs/sources/escalation-policies/configure-routes/index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/escalation-policies/configure-routes/ +canonical: https://grafana.com/docs/oncall/latest/escalation-policies/configure-routes/ keywords: - Grafana Cloud - Alerts @@ -10,7 +11,6 @@ keywords: - oncall - integrations title: Configure and manage routes -canonical: "https://grafana.com/docs/oncall/latest/escalation-policies/configure-routes/" weight: 300 --- diff --git a/docs/sources/get-started/_index.md b/docs/sources/get-started/_index.md index 06e190a7..677b203e 100644 --- a/docs/sources/get-started/_index.md +++ b/docs/sources/get-started/_index.md @@ -1,7 +1,8 @@ --- aliases: - - getting-started/ -canonical: https://grafana.com/docs/oncall/latest/getting-started/ + - /docs/oncall/latest/get-started/ + - /getting-started/ +canonical: https://grafana.com/docs/oncall/latest/get-started/ keywords: - Get started - On call diff --git a/docs/sources/integrations/_index.md b/docs/sources/integrations/_index.md index 061e4445..a49e7496 100644 --- a/docs/sources/integrations/_index.md +++ b/docs/sources/integrations/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/integrations/ +canonical: https://grafana.com/docs/oncall/latest/integrations/ keywords: - Grafana Cloud - Alerts @@ -10,7 +11,6 @@ keywords: - oncall - integrations title: Grafana OnCall integrations -canonical: "https://grafana.com/docs/oncall/latest/integrations/" weight: 500 --- diff --git a/docs/sources/integrations/available-integrations/_index.md b/docs/sources/integrations/available-integrations/_index.md index 61d02ae0..3d02ba76 100644 --- a/docs/sources/integrations/available-integrations/_index.md +++ b/docs/sources/integrations/available-integrations/_index.md @@ -1,6 +1,7 @@ --- aliases: - /docs/oncall/latest/integrations/available-integrations/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/ keywords: - Grafana Cloud - Alerts @@ -9,7 +10,6 @@ keywords: - Alertmanager - Prometheus title: Currently available integrations for Grafana OnCall -canonical: "https://grafana.com/docs/oncall/latest/integrations/available-integrations/" weight: 100 --- diff --git a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md index 1a0a6d7a..259d9e8d 100644 --- a/docs/sources/integrations/available-integrations/configure-alertmanager/index.md +++ b/docs/sources/integrations/available-integrations/configure-alertmanager/index.md @@ -1,7 +1,8 @@ --- aliases: - add-alertmanager/ -canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-alertmanager/ + - /docs/oncall/latest/integrations/available-integrations/configure-alertmanager/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/configure-alertmanager/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md index 608c4228..c3197649 100644 --- a/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md +++ b/docs/sources/integrations/available-integrations/configure-grafana-alerting/index.md @@ -1,7 +1,8 @@ --- aliases: - add-grafana-alerting/ -canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/ + - /docs/oncall/latest/integrations/available-integrations/configure-grafana-alerting/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/configure-grafana-alerting/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/available-integrations/configure-webhook/index.md b/docs/sources/integrations/available-integrations/configure-webhook/index.md index 0958279b..a762f006 100644 --- a/docs/sources/integrations/available-integrations/configure-webhook/index.md +++ b/docs/sources/integrations/available-integrations/configure-webhook/index.md @@ -2,7 +2,7 @@ aliases: - ../add-webhook-integration/ - /docs/oncall/latest/integrations/available-integrations/configure-webhook/ -canonical: https://grafana.com/docs/oncall/latest/integrations/add-webhook-integration/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/configure-webhook/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/available-integrations/configure-zabbix/index.md b/docs/sources/integrations/available-integrations/configure-zabbix/index.md index f0110edc..7fc0bb8e 100644 --- a/docs/sources/integrations/available-integrations/configure-zabbix/index.md +++ b/docs/sources/integrations/available-integrations/configure-zabbix/index.md @@ -2,7 +2,7 @@ aliases: - add-zabbix/ - /docs/oncall/latest/integrations/available-integrations/configure-zabbix/ -canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-zabbix/ +canonical: https://grafana.com/docs/oncall/latest/integrations/available-integrations/configure-zabbix/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/chatops-integrations/_index.md b/docs/sources/integrations/chatops-integrations/_index.md index b8d1fd76..c96e8d6e 100644 --- a/docs/sources/integrations/chatops-integrations/_index.md +++ b/docs/sources/integrations/chatops-integrations/_index.md @@ -2,7 +2,7 @@ aliases: - ../chat-options/ - /docs/oncall/latest/integrations/chatops-integrations/ -canonical: https://grafana.com/docs/oncall/latest/chat-options/ +canonical: https://grafana.com/docs/oncall/latest/integrations/chatops-integrations/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/chatops-integrations/configure-slack/index.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md index 57dfe60c..33d29016 100644 --- a/docs/sources/integrations/chatops-integrations/configure-slack/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -2,7 +2,7 @@ aliases: - ../../chat-options/configure-slack/ - /docs/oncall/latest/integrations/chatops-integrations/configure-slack/ -canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-slack/ +canonical: https://grafana.com/docs/oncall/latest/integrations/chatops-integrations/configure-slack/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/chatops-integrations/configure-teams/index.md b/docs/sources/integrations/chatops-integrations/configure-teams/index.md index 76178c87..b4f42450 100644 --- a/docs/sources/integrations/chatops-integrations/configure-teams/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-teams/index.md @@ -2,7 +2,7 @@ aliases: - ../../chat-options/configure-teams/ - /docs/oncall/latest/integrations/chatops-integrations/configure-teams/ -canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-teams/ +canonical: https://grafana.com/docs/oncall/latest/integrations/chatops-integrations/configure-teams/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md index 0720e56b..fd510340 100644 --- a/docs/sources/integrations/chatops-integrations/configure-telegram/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-telegram/index.md @@ -2,7 +2,7 @@ aliases: - ../../chat-options/configure-telegram/ - /docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ -canonical: https://grafana.com/docs/oncall/latest/chat-options/configure-telegram/ +canonical: https://grafana.com/docs/oncall/latest/integrations/chatops-integrations/configure-telegram/ keywords: - Grafana Cloud - Alerts diff --git a/docs/sources/oncall-api-reference/_index.md b/docs/sources/oncall-api-reference/_index.md index 9f998e59..cc453fec 100644 --- a/docs/sources/oncall-api-reference/_index.md +++ b/docs/sources/oncall-api-reference/_index.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/ title: Grafana OnCall HTTP API reference -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/" weight: 1500 --- diff --git a/docs/sources/oncall-api-reference/alertgroups.md b/docs/sources/oncall-api-reference/alertgroups.md index 76be1aca..e578644d 100644 --- a/docs/sources/oncall-api-reference/alertgroups.md +++ b/docs/sources/oncall-api-reference/alertgroups.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/alertgroups/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/alertgroups/ title: Alert groups HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/alertgroups/" weight: 400 --- diff --git a/docs/sources/oncall-api-reference/alerts.md b/docs/sources/oncall-api-reference/alerts.md index be215ed8..d67f8e14 100644 --- a/docs/sources/oncall-api-reference/alerts.md +++ b/docs/sources/oncall-api-reference/alerts.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/alerts/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/alerts/ title: Alerts HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/alerts/" weight: 100 --- diff --git a/docs/sources/oncall-api-reference/escalation_chains.md b/docs/sources/oncall-api-reference/escalation_chains.md index 29638dbd..f88b8d71 100644 --- a/docs/sources/oncall-api-reference/escalation_chains.md +++ b/docs/sources/oncall-api-reference/escalation_chains.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/escalation_chains/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_chains/ title: Escalation Chains HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_chains/" weight: 200 --- diff --git a/docs/sources/oncall-api-reference/escalation_policies.md b/docs/sources/oncall-api-reference/escalation_policies.md index 06e366fb..26c8a470 100644 --- a/docs/sources/oncall-api-reference/escalation_policies.md +++ b/docs/sources/oncall-api-reference/escalation_policies.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/escalation_policies/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_policies/ title: Escalation Policies HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/escalation_policies/" weight: 300 --- diff --git a/docs/sources/oncall-api-reference/integrations.md b/docs/sources/oncall-api-reference/integrations.md index 0f7af01d..282e39df 100644 --- a/docs/sources/oncall-api-reference/integrations.md +++ b/docs/sources/oncall-api-reference/integrations.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/integrations/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/integrations/ title: Integrations HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/integrations/" weight: 500 --- diff --git a/docs/sources/oncall-api-reference/on_call_shifts.md b/docs/sources/oncall-api-reference/on_call_shifts.md index 37bc19b2..f06a8d12 100644 --- a/docs/sources/oncall-api-reference/on_call_shifts.md +++ b/docs/sources/oncall-api-reference/on_call_shifts.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/on_call_shifts/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/on_call_shifts/ title: OnCall shifts HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/on_call_shifts/" weight: 600 --- diff --git a/docs/sources/oncall-api-reference/outgoing_webhooks.md b/docs/sources/oncall-api-reference/outgoing_webhooks.md index a9d7fec1..deb26150 100644 --- a/docs/sources/oncall-api-reference/outgoing_webhooks.md +++ b/docs/sources/oncall-api-reference/outgoing_webhooks.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/outgoing_webhooks/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/outgoing_webhooks/ title: Outgoing webhooks HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/outgoing_webhooks/" weight: 700 --- diff --git a/docs/sources/oncall-api-reference/personal_notification_rules.md b/docs/sources/oncall-api-reference/personal_notification_rules.md index 2165d1c3..fdf92646 100644 --- a/docs/sources/oncall-api-reference/personal_notification_rules.md +++ b/docs/sources/oncall-api-reference/personal_notification_rules.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/personal_notification_rules/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/ title: Personal Notification Rules HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/" weight: 800 --- diff --git a/docs/sources/oncall-api-reference/postmortem_messages.md b/docs/sources/oncall-api-reference/postmortem_messages.md index 4046b11f..f3f5aff8 100644 --- a/docs/sources/oncall-api-reference/postmortem_messages.md +++ b/docs/sources/oncall-api-reference/postmortem_messages.md @@ -1,9 +1,9 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/postmortem_messages/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/postmortem_messages/ draft: true title: Postmortem Messages HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/postmortem_messages/" weight: 900 --- diff --git a/docs/sources/oncall-api-reference/postmortems.md b/docs/sources/oncall-api-reference/postmortems.md index 7393201d..91797f74 100644 --- a/docs/sources/oncall-api-reference/postmortems.md +++ b/docs/sources/oncall-api-reference/postmortems.md @@ -1,9 +1,9 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/postmortems/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/postmortems/ draft: true title: Postmortem HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/postmortems/" weight: 1000 --- diff --git a/docs/sources/oncall-api-reference/routes.md b/docs/sources/oncall-api-reference/routes.md index bff5d3d0..5f92e79a 100644 --- a/docs/sources/oncall-api-reference/routes.md +++ b/docs/sources/oncall-api-reference/routes.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/routes/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/routes/ title: Routes HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/routes/" weight: 1100 --- diff --git a/docs/sources/oncall-api-reference/schedules.md b/docs/sources/oncall-api-reference/schedules.md index c42f6bce..53b19f81 100644 --- a/docs/sources/oncall-api-reference/schedules.md +++ b/docs/sources/oncall-api-reference/schedules.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/schedules/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/schedules/ title: Schedule HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/schedules/" weight: 1200 --- diff --git a/docs/sources/oncall-api-reference/slack_channels.md b/docs/sources/oncall-api-reference/slack_channels.md index 887db569..f5516688 100644 --- a/docs/sources/oncall-api-reference/slack_channels.md +++ b/docs/sources/oncall-api-reference/slack_channels.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/slack_channels/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/slack_channels/ title: Slack Channels HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/slack_channels/" weight: 1300 --- diff --git a/docs/sources/oncall-api-reference/user_groups.md b/docs/sources/oncall-api-reference/user_groups.md index 5a8815d3..c078eb13 100644 --- a/docs/sources/oncall-api-reference/user_groups.md +++ b/docs/sources/oncall-api-reference/user_groups.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/user_groups/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/user_groups/ title: OnCall User Groups HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/user_groups/" weight: 1400 --- diff --git a/docs/sources/oncall-api-reference/users.md b/docs/sources/oncall-api-reference/users.md index c601cb42..4c520eeb 100644 --- a/docs/sources/oncall-api-reference/users.md +++ b/docs/sources/oncall-api-reference/users.md @@ -1,8 +1,8 @@ --- aliases: - /docs/oncall/latest/oncall-api-reference/users/ +canonical: https://grafana.com/docs/oncall/latest/oncall-api-reference/users/ title: Grafana OnCall Users HTTP API -canonical: "https://grafana.com/docs/oncall/latest/oncall-api-reference/users/" weight: 1500 --- From 78025c2a604d1eb8335780bf2d8ff90d8ffd4693 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Thu, 27 Oct 2022 14:01:07 -0400 Subject: [PATCH 17/50] Fix spelling of Alertmanager Signed-off-by: Jack Baldry --- docs/sources/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/_index.md b/docs/sources/_index.md index 92f9d004..04026cb9 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -21,7 +21,7 @@ weight: 1000 Grafana OnCall is an open source incident response management tool built to help teams improve their collaboration and resolve incidents faster. Some of the core strengths of Grafana OnCall include: -- **Support for a broad set of monitoring systems:** Grafana OnCall supports integrations with many monitoring systems, including Grafana, Prometheus, AlertManager, Zabbix, and more. +- **Support for a broad set of monitoring systems:** Grafana OnCall supports integrations with many monitoring systems, including Grafana, Prometheus, Alertmanager, Zabbix, and more. - **Reduce alert noise:** Automatic alert grouping helps avoid alert storms and reduce noise during incidents. Auto-resolve settings can resolve without human intervention when the resolve conditions are met, enabling you to control alert noise and reduce alert fatigue. - **Automatic escalation to on-call rotations:** Grafana OnCall’s flexible calendar integration allows you to define your on-call rotations while managing on-call schedules in your preferred calendar application with iCal format. Configurable alert escalation automatically escalates alerts to on-call team members, notifies slack channels, and more. - **ChatOps focused:** Grafana OnCall integrates closely with your slack workspace to deliver alert notifications to individuals and groups, making daily alerts more visible and easier to manage. From 85c3fbc8b8ce5d02b1c75b2e62cd63bb2e33a05b Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Mon, 31 Oct 2022 12:30:04 +0100 Subject: [PATCH 18/50] linting fixes --- .../containers/SlackInstructions/SlackInstructions.tsx | 6 +++--- .../TelegramIntegrationButton.tsx | 2 +- .../UserSettings/parts/tabs/SlackTab/SlackTab.tsx | 2 +- .../UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx | 6 +++--- grafana-plugin/src/icons/index.tsx | 2 +- .../chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx | 2 +- .../parts/tabs/TelegramSettings/TelegramSettings.tsx | 8 +++----- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx index e4465c0e..edd7a21b 100644 --- a/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx +++ b/grafana-plugin/src/containers/SlackInstructions/SlackInstructions.tsx @@ -1,12 +1,12 @@ -import React, { useCallback, useState, FC } from 'react'; +import React, { 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 { SlackNewIcon } from 'icons'; import styles from './SlackInstructions.module.css'; @@ -14,7 +14,7 @@ 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) => { +const SlackInstructions: FC = observer(() => { return (
    diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx index 19a28911..4c307180 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.tsx @@ -5,8 +5,8 @@ import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import CopyToClipboard from 'react-copy-to-clipboard'; -import Text from 'components/Text/Text'; import Block from 'components/GBlock/Block'; +import Text from 'components/Text/Text'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { useStore } from 'state/useStore'; import { UserAction } from 'state/userAction'; 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 e64d750d..3a305949 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/SlackTab/SlackTab.tsx @@ -3,8 +3,8 @@ import React, { useCallback } from 'react'; 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 Text from 'components/Text/Text'; import { SlackNewIcon } from 'icons'; import { useStore } from 'state/useStore'; diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx index 59cb8840..f0750886 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx @@ -1,17 +1,17 @@ import React, { HTMLAttributes, useEffect, useState } from 'react'; -import { Alert, Button, HorizontalGroup, Icon, VerticalGroup, Field, Input } from '@grafana/ui'; +import { Button, Icon, VerticalGroup, Field, Input } from '@grafana/ui'; import cn from 'classnames/bind'; import { observer } from 'mobx-react'; import CopyToClipboard from 'react-copy-to-clipboard'; +import Block from 'components/GBlock/Block'; import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; -import Block from 'components/GBlock/Block'; +import { TelegramColorIcon } from 'icons'; import { AppFeature } from 'state/features'; import { useStore } from 'state/useStore'; import { openNotification } from 'utils'; -import { TelegramColorIcon } from 'icons'; import styles from './TelegramInfo.module.css'; diff --git a/grafana-plugin/src/icons/index.tsx b/grafana-plugin/src/icons/index.tsx index 0de9316c..c5675c28 100644 --- a/grafana-plugin/src/icons/index.tsx +++ b/grafana-plugin/src/icons/index.tsx @@ -293,7 +293,7 @@ export const TelegramColorIcon = () => { ); }; -export const SlackNewIcon = (props: IconProps) => ( +export const SlackNewIcon = () => ( Date: Mon, 31 Oct 2022 12:43:25 +0100 Subject: [PATCH 19/50] prettier fix --- .../TelegramIntegrationButton.module.css | 2 +- .../UserSettings/parts/tabs/SlackTab/SlackTab.module.css | 2 +- .../parts/tabs/TelegramInfo/TelegramInfo.module.css | 2 +- .../parts/tabs/TelegramSettings/TelegramSettings.module.css | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css index a9a45ed3..8ec18f73 100644 --- a/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css +++ b/grafana-plugin/src/containers/TelegramIntegrationButton/TelegramIntegrationButton.module.css @@ -33,4 +33,4 @@ .infoblock-text { margin-left: 48px; margin-right: 48px; -} \ No newline at end of file +} 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 53d1112f..51473abb 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 @@ -11,4 +11,4 @@ .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/TelegramInfo/TelegramInfo.module.css b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css index 6f0a61b7..fea6889e 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.module.css @@ -17,4 +17,4 @@ .field-command { width: 100%; display: inline-block; -} \ No newline at end of file +} diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css index 4aef28a6..ecd192db 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css @@ -24,4 +24,4 @@ ul { .infoblock-icon { margin-top: 24px; -} \ No newline at end of file +} From 64570cc9fde7b336e5807ee1783f09f907614bcd Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Mon, 31 Oct 2022 11:08:33 -0300 Subject: [PATCH 20/50] Update schedules endpoint to filter by numeric type --- engine/apps/api/tests/test_schedules.py | 4 ++-- engine/apps/api/views/schedule.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/engine/apps/api/tests/test_schedules.py b/engine/apps/api/tests/test_schedules.py index 4c562307..9c73027d 100644 --- a/engine/apps/api/tests/test_schedules.py +++ b/engine/apps/api/tests/test_schedules.py @@ -205,11 +205,11 @@ def test_get_list_schedules_by_type( }, ] - for expected, schedule_type in enumerate(("api", "ical", "web")): + for schedule_type in range(3): url = reverse("api-internal:schedule-list") + "?type={}".format(schedule_type) response = client.get(url, format="json", **make_user_auth_headers(user, token)) assert response.status_code == status.HTTP_200_OK - assert response.json() == [expected_payload[expected]] + assert response.json() == [expected_payload[schedule_type]] @pytest.mark.django_db diff --git a/engine/apps/api/views/schedule.py b/engine/apps/api/views/schedule.py index 5a2d96aa..04de4393 100644 --- a/engine/apps/api/views/schedule.py +++ b/engine/apps/api/views/schedule.py @@ -24,7 +24,7 @@ from apps.api.serializers.schedule_polymorphic import ( from apps.auth_token.auth import PluginAuthentication from apps.auth_token.constants import SCHEDULE_EXPORT_TOKEN_NAME from apps.auth_token.models import ScheduleExportAuthToken -from apps.schedules.models import OnCallSchedule, OnCallScheduleCalendar, OnCallScheduleICal, OnCallScheduleWeb +from apps.schedules.models import OnCallSchedule from apps.slack.models import SlackChannel from apps.slack.tasks import update_slack_user_group_for_schedules from common.api_helpers.exceptions import BadRequest, Conflict @@ -42,7 +42,9 @@ EVENTS_FILTER_BY_ROTATION = "rotation" EVENTS_FILTER_BY_OVERRIDE = "override" EVENTS_FILTER_BY_FINAL = "final" -SCHEDULE_TYPE_TO_CLASS = {"api": OnCallScheduleCalendar, "ical": OnCallScheduleICal, "web": OnCallScheduleWeb} +SCHEDULE_TYPE_TO_CLASS = { + str(num_type): cls for cls, num_type in PolymorphicScheduleSerializer.SCHEDULE_CLASS_TO_TYPE.items() +} class ScheduleView( From 4e8982cc81df0673b37d3926cc53c8560e2b02a6 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 1 Nov 2022 16:38:04 +0800 Subject: [PATCH 21/50] Add management command to manually verify phone numbers (#743) * Add cmd to manully verify phone numbers * Rework verify_phone command --- .../management/commands/verify_phone.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 engine/engine/management/commands/verify_phone.py diff --git a/engine/engine/management/commands/verify_phone.py b/engine/engine/management/commands/verify_phone.py new file mode 100644 index 00000000..80e6a7d7 --- /dev/null +++ b/engine/engine/management/commands/verify_phone.py @@ -0,0 +1,52 @@ +from django.core.management.base import BaseCommand + +from apps.twilioapp.twilio_client import twilio_client +from apps.twilioapp.utils import check_phone_number_is_valid +from apps.user_management.models import User + + +class Command(BaseCommand): + """ + This command is to manually verify user's phone numbers. + """ + + def add_arguments(self, parser): + parser.add_argument("user_id", type=int, help="User id to manually verify phone number") + parser.add_argument("phone_number", type=str, help="Phone number to verify") + + parser.add_argument( + "--override", + action="store_true", + help="Override existing phone number", + ) + + def handle(self, *args, **options): + user_id = options["user_id"] + phone_number = options["phone_number"] + + if not check_phone_number_is_valid(phone_number): + self.stdout.write(self.style.ERROR('Invalid phone number "%s"' % phone_number)) + return + + try: + user = User.objects.get(pk=user_id) + except User.objects.DoesNotExists: + self.stdout.write(self.style.ERROR('Invalid user_id "%s"' % user_id)) + return + + if user.verified_phone_number and not options["override"]: + self.stdout.write(self.style.ERROR('User "%s" already has a phone number' % user_id)) + return + + normalized_phone_number, _ = twilio_client.normalize_phone_number_via_twilio(phone_number) + if normalized_phone_number: + user.save_verified_phone_number(normalized_phone_number) + user.unverified_phone_number = phone_number + user.save(update_fields=["unverified_phone_number"]) + else: + self.stdout.write(self.style.ERROR('Invalid phone number "%s"' % phone_number)) + return + + self.stdout.write( + self.style.SUCCESS('Successfully verified phone number "%s" for user "%s"' % (phone_number, user_id)) + ) From fd8ad47a55741b7833f4113532ada1dfd77f69d8 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 1 Nov 2022 17:13:39 +0800 Subject: [PATCH 22/50] Modify CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c361a3c..788d3967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v1.0.47 (2022-11-01) + +- Add verify_phone manage command + ## v1.0.46 (2022-10-28) - Bug fixes From 001a760be7074b75d4edde1a1db0450867b2559c Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 1 Nov 2022 17:23:29 +0800 Subject: [PATCH 23/50] Update manual_incident.py --- engine/apps/slack/scenarios/manual_incident.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/apps/slack/scenarios/manual_incident.py b/engine/apps/slack/scenarios/manual_incident.py index bb494aac..d5f59404 100644 --- a/engine/apps/slack/scenarios/manual_incident.py +++ b/engine/apps/slack/scenarios/manual_incident.py @@ -365,7 +365,7 @@ def _get_manual_incident_form_view(routing_uid, blocks, private_metatada): "callback_id": routing_uid, "title": { "type": "plain_text", - "text": "Create an Incident", + "text": "Start New Escalation", }, "close": { "type": "plain_text", From 882851e0b8635b0a911ff0aa07279ba5225fb1d0 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 1 Nov 2022 17:39:18 +0800 Subject: [PATCH 24/50] Revert "Modify CHANGELOG.md" This reverts commit fd8ad47a55741b7833f4113532ada1dfd77f69d8. --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 788d3967..7c361a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # Change Log -## v1.0.47 (2022-11-01) - -- Add verify_phone manage command - ## v1.0.46 (2022-10-28) - Bug fixes From 43f5ed9326100040c931ca6720f0d94803b31048 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 1 Nov 2022 10:57:58 +0100 Subject: [PATCH 25/50] live settings request for slack is fixed --- .../chat-ops/parts/tabs/SlackSettings/SlackSettings.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 e09f41f8..b48da2ec 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 @@ -36,9 +36,14 @@ class SlackSettings extends Component { }; componentDidMount() { - this.getSlackLiveSettings().then(() => { + const { store } = this.props; + if (store.hasFeature(AppFeature.LiveSettings)) { + this.getSlackLiveSettings().then(() => { + this.update(); + }); + } else { this.update(); - }); + } } handleOpenSlackInstructions = () => { From 80f76e9c41f88cf38a7337c6495c100496291678 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 1 Nov 2022 10:57:58 +0100 Subject: [PATCH 26/50] Ackno --- .../parts/tabs/SlackSettings/SlackSettings.tsx | 17 ++++++++++++----- .../tabs/TelegramSettings/TelegramSettings.tsx | 8 +++++--- 2 files changed, 17 insertions(+), 8 deletions(-) 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 e09f41f8..3bd3e13e 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 @@ -36,9 +36,14 @@ class SlackSettings extends Component { }; componentDidMount() { - this.getSlackLiveSettings().then(() => { + const { store } = this.props; + if (store.hasFeature(AppFeature.LiveSettings)) { + this.getSlackLiveSettings().then(() => { + this.update(); + }); + } else { this.update(); - }); + } } handleOpenSlackInstructions = () => { @@ -262,9 +267,11 @@ class SlackSettings extends Component { - - - + {store.hasFeature(AppFeature.LiveSettings) && ( + + + + )} )} diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx index 8d363099..79db0f35 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx @@ -111,9 +111,11 @@ class TelegramSettings extends Component { - - - + {store.hasFeature(AppFeature.LiveSettings) && ( + + + + )} ); From 6d5853d1fffc8ea18ad5eeb323d4266082b2aa94 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 1 Nov 2022 19:02:10 +0800 Subject: [PATCH 27/50] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c361a3c..e8260e15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## v1.0.478(2022-10-28) + +- verify_number management command +- chatops page redesign + +## v1.0.47 (2022-10-28) + +- Bug fixes + ## v1.0.46 (2022-10-28) - Bug fixes From ca6f7ef691909ac2ad113d121bc58d8af2803ac7 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Tue, 1 Nov 2022 19:03:01 +0800 Subject: [PATCH 28/50] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8260e15..868b6262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Change Log -## v1.0.478(2022-10-28) +## v1.0.48 (2022-11-01) - verify_number management command - chatops page redesign -## v1.0.47 (2022-10-28) +## v1.0.47 (2022-11-01) - Bug fixes From 035584f17e9390117a2b4fcca60136dc331681f0 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 1 Nov 2022 13:48:36 +0000 Subject: [PATCH 29/50] Enable email backend by default (#740) * enable email backend by default, catch empty EMAIL_HOST * fix tests * fix tests * fix tests --- engine/apps/email/tasks.py | 14 ++++++++ engine/apps/email/tests/test_notify_user.py | 39 +++++++++++++++++++++ engine/settings/base.py | 2 +- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/engine/apps/email/tasks.py b/engine/apps/email/tasks.py index 3fdf4d4e..d9054745 100644 --- a/engine/apps/email/tasks.py +++ b/engine/apps/email/tasks.py @@ -39,6 +39,20 @@ def notify_user_async(user_pk, alert_group_pk, notification_policy_pk): logger.warning(f"User notification policy {notification_policy_pk} does not exist") return + # create an error log in case EMAIL_HOST is not specified + if not live_settings.EMAIL_HOST: + UserNotificationPolicyLogRecord.objects.create( + author=user, + type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED, + notification_policy=notification_policy, + alert_group=alert_group, + reason="Error while sending email", + notification_step=notification_policy.step, + notification_channel=notification_policy.notify_by, + ) + logger.error(f"Error while sending email: empty EMAIL_HOST env variable") + return + emails_left = user.organization.emails_left(user) if emails_left <= 0: UserNotificationPolicyLogRecord.objects.create( diff --git a/engine/apps/email/tests/test_notify_user.py b/engine/apps/email/tests/test_notify_user.py index a6b1c356..f5a10601 100644 --- a/engine/apps/email/tests/test_notify_user.py +++ b/engine/apps/email/tests/test_notify_user.py @@ -24,6 +24,7 @@ def test_notify_user( make_user_notification_policy, ): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = "test" organization = make_organization() user = make_user_for_organization(organization) @@ -44,6 +45,42 @@ def test_notify_user( assert len(mail.outbox) == 1 +@pytest.mark.django_db +def test_notify_empty_email_host( + settings, + make_organization, + make_user_for_organization, + make_token_for_organization, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_user_notification_policy, +): + settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = None + + organization = make_organization() + user = make_user_for_organization(organization) + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload) + + notification_policy = make_user_notification_policy( + user, + UserNotificationPolicy.Step.NOTIFY, + notify_by=8, + important=False, + ) + + notify_user_async(user.pk, alert_group.pk, notification_policy.pk) + assert len(mail.outbox) == 0 + + log_record = notification_policy.personal_log_records.last() + assert log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED + + @pytest.mark.django_db def test_notify_user_bad_smtp_host( settings, @@ -56,6 +93,7 @@ def test_notify_user_bad_smtp_host( make_user_notification_policy, ): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = "test" organization = make_organization() user = make_user_for_organization(organization) @@ -93,6 +131,7 @@ def test_notify_user_no_emails_left( make_user_notification_policy, ): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = "test" organization = make_organization() user = make_user_for_organization(organization) diff --git a/engine/settings/base.py b/engine/settings/base.py index 6235c3e3..e701ea24 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -50,7 +50,7 @@ BASE_URL = os.environ.get("BASE_URL") # Root URL of OnCall backend # Feature toggles FEATURE_LIVE_SETTINGS_ENABLED = getenv_boolean("FEATURE_LIVE_SETTINGS_ENABLED", default=True) FEATURE_TELEGRAM_INTEGRATION_ENABLED = getenv_boolean("FEATURE_TELEGRAM_INTEGRATION_ENABLED", default=True) -FEATURE_EMAIL_INTEGRATION_ENABLED = getenv_boolean("FEATURE_EMAIL_INTEGRATION_ENABLED", default=False) +FEATURE_EMAIL_INTEGRATION_ENABLED = getenv_boolean("FEATURE_EMAIL_INTEGRATION_ENABLED", default=True) FEATURE_SLACK_INTEGRATION_ENABLED = getenv_boolean("FEATURE_SLACK_INTEGRATION_ENABLED", default=True) FEATURE_WEB_SCHEDULES_ENABLED = getenv_boolean("FEATURE_WEB_SCHEDULES_ENABLED", default=False) GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED = getenv_boolean("GRAFANA_CLOUD_ONCALL_HEARTBEAT_ENABLED", default=True) From 416951b36926685f010a434450f0fbf68eb50f73 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 1 Nov 2022 15:31:13 +0100 Subject: [PATCH 30/50] navigation css fix (#750) * navigation css fix * linting error fix --- .../TelegramSettings/TelegramSettings.module.css | 2 +- .../parts/tabs/TelegramSettings/TelegramSettings.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css index ecd192db..158c14a2 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.module.css @@ -12,7 +12,7 @@ width: 725px; } -ul { +.features-list > ul { margin: 20px 30px; } diff --git a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx index 79db0f35..c6eb5dc5 100644 --- a/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx +++ b/grafana-plugin/src/pages/chat-ops/parts/tabs/TelegramSettings/TelegramSettings.tsx @@ -102,11 +102,13 @@ class TelegramSettings extends Component { Features -
      -
    • perform actions (acknowledge, resolve, silence)
    • -
    • discuss alerts in comments
    • -
    • notifications to users accounts will be served as links to the main channel
    • -
    +
    +
      +
    • perform actions (acknowledge, resolve, silence)
    • +
    • discuss alerts in comments
    • +
    • notifications to users accounts will be served as links to the main channel
    • +
    +
    Make sure your team connects Telegram in their OnCall user profiles too or they cannot manage alert groups.
    From ae485bededf7cd4c99a53e0c7a25e8432792a0bf Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Tue, 1 Nov 2022 14:34:31 +0000 Subject: [PATCH 31/50] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 868b6262..5deaff07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## v1.0.49 (2022-11-01) + +- Enable SMTP email backend by default +- Fix Grafana sidebar frontend bug + ## v1.0.48 (2022-11-01) - verify_number management command From 28bfd7b0b2149e6f08f8281304bf7ddb37976c0b Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Wed, 2 Nov 2022 01:07:18 -0300 Subject: [PATCH 32/50] Update pg docker developer compose to setup grafana using pg db (#751) * Update pg docker developer compose to setup grafana using pg db * Rework postgres 'create if not exists' grafana db --- docker-compose-developer-pg.yml | 47 +++++++++++++++------------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/docker-compose-developer-pg.yml b/docker-compose-developer-pg.yml index f42f17e3..7c61cf35 100644 --- a/docker-compose-developer-pg.yml +++ b/docker-compose-developer-pg.yml @@ -15,6 +15,11 @@ services: limits: memory: 500m cpus: '0.5' + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 10s + timeout: 5s + retries: 5 redis: image: redis @@ -42,37 +47,27 @@ services: - "15672:15672" - "5672:5672" - mysql-to-create-grafana-db: - image: mysql:5.7 - platform: linux/x86_64 - command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci - restart: always - ports: - - "3306:3306" - environment: - MYSQL_ROOT_PASSWORD: empty - MYSQL_DATABASE: grafana - deploy: - resources: - limits: - memory: 500m - cpus: '0.5' - healthcheck: - test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] - timeout: 20s - retries: 10 + postgres_to_create_grafana_db: + image: postgres:14.4 + command: bash -c "PGPASSWORD=empty psql -U postgres -h postgres -tc \"SELECT 1 FROM pg_database WHERE datname = 'grafana'\" | grep -q 1 || PGPASSWORD=empty psql -U postgres -h postgres -c \"CREATE DATABASE grafana\"" + depends_on: + postgres: + condition: service_healthy grafana: image: "grafana/grafana:main" restart: always environment: - GF_DATABASE_TYPE: mysql - GF_DATABASE_HOST: mysql - GF_DATABASE_USER: root + GF_DATABASE_TYPE: postgres + GF_DATABASE_HOST: postgres:5432 + GF_DATABASE_NAME: grafana + GF_DATABASE_USER: postgres GF_DATABASE_PASSWORD: empty - GF_SECURITY_ADMIN_USER: oncall - GF_SECURITY_ADMIN_PASSWORD: oncall + GF_DATABASE_SSL_MODE: disable + GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin} GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app + GF_INSTALL_PLUGINS: grafana-oncall-app deploy: resources: limits: @@ -83,5 +78,7 @@ services: ports: - "3000:3000" depends_on: - mysql-to-create-grafana-db: + postgres_to_create_grafana_db: + condition: service_completed_successfully + postgres: condition: service_healthy From d3dfaebab73d30de18909f67eae8b374d451cc72 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 2 Nov 2022 08:34:41 +0100 Subject: [PATCH 33/50] Helm chart: add support for PostgreSQL (#661) * add support for PostgreSQL Signed-off-by: DavidSpek * disable postgres by default Signed-off-by: DavidSpek * fix external postgres existing secret Signed-off-by: DavidSpek * disable ingress annotations and tls by default Signed-off-by: DavidSpek * fix exec command in notes Signed-off-by: DavidSpek * Add review comments + cleanup README Signed-off-by: DavidSpek Signed-off-by: DavidSpek --- helm/oncall/Chart.lock | 24 +++++ helm/oncall/Chart.yaml | 4 + helm/oncall/README.md | 92 +++++++++++++++--- helm/oncall/charts/postgresql-11.9.10.tgz | Bin 0 -> 57385 bytes helm/oncall/templates/NOTES.txt | 2 +- helm/oncall/templates/_env.tpl | 68 +++++++++++++ helm/oncall/templates/_helpers.tpl | 22 +++++ helm/oncall/templates/celery/_deployment.tpl | 10 ++ helm/oncall/templates/engine/deployment.yaml | 10 ++ helm/oncall/templates/engine/job-migrate.yaml | 14 +++ helm/oncall/templates/secrets.yaml | 12 ++- helm/oncall/values.yaml | 27 ++++- 12 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 helm/oncall/Chart.lock create mode 100644 helm/oncall/charts/postgresql-11.9.10.tgz diff --git a/helm/oncall/Chart.lock b/helm/oncall/Chart.lock new file mode 100644 index 00000000..a91cbcb0 --- /dev/null +++ b/helm/oncall/Chart.lock @@ -0,0 +1,24 @@ +dependencies: +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.8.0 +- name: mariadb + repository: https://charts.bitnami.com/bitnami + version: 11.0.10 +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 11.9.10 +- name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 10.1.1 +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 16.10.1 +- name: grafana + repository: https://grafana.github.io/helm-charts + version: 6.29.6 +- name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.1.4 +digest: sha256:8e17f2f6a087b6db52670458fc0e1cb39b0a3f7962ff7ebbc7be4c982a4e1720 +generated: "2022-10-18T11:22:39.061819+02:00" diff --git a/helm/oncall/Chart.yaml b/helm/oncall/Chart.yaml index f7708ca6..55564328 100644 --- a/helm/oncall/Chart.yaml +++ b/helm/oncall/Chart.yaml @@ -25,6 +25,10 @@ dependencies: version: 11.0.10 repository: https://charts.bitnami.com/bitnami condition: mariadb.enabled + - name: postgresql + version: 11.9.10 + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled - name: rabbitmq version: 10.1.1 repository: https://charts.bitnami.com/bitnami diff --git a/helm/oncall/README.md b/helm/oncall/README.md index 87a41f0d..d64e6c89 100644 --- a/helm/oncall/README.md +++ b/helm/oncall/README.md @@ -6,23 +6,27 @@ It will also deploy cert manager and nginx ingress controller, as Grafana OnCall to receive alerts from other monitoring systems. Grafana OnCall engine acts as a backend and can be connected to the Grafana frontend plugin named Grafana OnCall. Architecture diagram can be found [here](https://raw.githubusercontent.com/grafana/oncall/dev/docs/img/architecture_diagram.png) +## Production usage -### Production usage **Default helm chart configuration is not intended for production.** The helm chart includes all the services into a single release, which is not recommended for production usage. It is recommended to run stateful services such as MySQL and RabbitMQ separately from this release or use managed PaaS solutions. It will significantly reduce the overhead of managing them. Here are the instructions on how to set up your own [ingress](#set-up-external-access), [MySQL](#connect-external-mysql), [RabbitMQ](#connect-external-rabbitmq), [Redis](#connect-external-redis) - ### Cluster requirements + * ensure you can run x86-64/amd64 workloads. arm64 architecture is currently not supported * kubernetes version 1.25+ is not supported, if cert-manager is enabled ## Install + ### Prepare the repo -``` + +```bash # Add the repository helm repo add grafana https://grafana.github.io/helm-charts helm repo update ``` + ### Installing the helm chart + ```bash # Install the chart helm install \ @@ -34,7 +38,8 @@ helm install \ ``` Follow the `helm install` output to finish setting up Grafana OnCall backend and Grafana OnCall frontend plugin e.g. -``` + +```bash 👋 Your Grafana OnCall instance has been successfully deployed ❗ Set up a DNS record for your domain (use A Record and "@" to point a root domain to the IP address) @@ -73,6 +78,7 @@ Follow the `helm install` output to finish setting up Grafana OnCall backend and ## Configuration You can edit values.yml to make changes to the helm chart configuration and re-deploy the release with the following command: + ```bash helm upgrade \ --install \ @@ -87,7 +93,7 @@ helm upgrade \ You can set up Slack connection via following variables: -``` +```yaml oncall: slack: enabled: true @@ -103,7 +109,7 @@ oncall: To set up Telegram tokem and webhook url use: -``` +```yaml oncall: telegram: enabled: true @@ -112,13 +118,14 @@ oncall: ``` ### Set up external access + Grafana OnCall can be connected to the external monitoring systems or grafana deployed to the other cluster. Nginx Ingress Controller and Cert Manager charts are included in the helm chart with the default configuration. -If you set the DNS A Record pointing to the external IP address of the installation with the Hostname matching base_url parameter, https will be automatically set up. If grafana is enabled in the chart values, it will also be available on https:///grafana/. See the details in `helm install` output. +If you set the DNS A Record pointing to the external IP address of the installation with the Hostname matching base_url parameter, https will be automatically set up. If grafana is enabled in the chart values, it will also be available on `https:///grafana/`. See the details in `helm install` output. To use a different ingress controller or tls certificate management system, set the following values to false and edit ingress settings -``` +```yaml ingress-nginx: enabled: false @@ -132,18 +139,36 @@ ingress: cert-manager.io/issuer: "letsencrypt-prod" ``` +### Use PostgreSQL instead of MySQL + +It is possible to use PostgreSQL instead of MySQL. To do so, set mariadb.enabled to `false`, +postgresql.enabled to `true` and database.type to `postgresql`. + +```yaml +mariadb: + enabled: false + +postgresql: + enabled: true + +database: + type: postgresql +``` + ### Connect external MySQL It is recommended to use the managed MySQL 5.7 database provided by your cloud provider Make sure to create the database with the following parameters before installing this chart -``` + +```sql CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` -To use an external MySQL instance set mysql.enabled to `false` and configure the `externalMysql` parameters. -``` +To use an external MySQL instance set mariadb.enabled to `false` and configure the `externalMysql` parameters. + +```yaml mariadb: - enabled: true + enabled: false # Make sure to create the database with the following parameters: # CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -155,13 +180,42 @@ externalMysql: password: ``` +### Connect external PostgreSQL + +To use an external PostgreSQL instance set mariadb.enabled to `false`, +postgresql.enabled to `false`, database.type to `postgresql` and configure +the `externalPostgresql` parameters. + +```yaml +mariadb: + enabled: false + +postgresql: + enabled: false + +database: + type: postgresql + +# Make sure to create the database with the following parameters: +# CREATE DATABASE oncall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +externalPostgresql: + host: + port: + db_name: + user: + password: + existingSecret: "" + passwordKey: password + ``` + ### Connect external RabbitMQ Option 1. Install RabbitMQ separately into the cluster using the [official documentation](https://www.rabbitmq.com/kubernetes/operator/operator-overview.html) Option 2. Use managed solution such as [CloudAMPQ](https://www.cloudamqp.com/) To use an external RabbitMQ instance set rabbitmq.enabled to `false` and configure the `externalRabbitmq` parameters. -``` + +```yaml rabbitmq: enabled: false # Disable the RabbitMQ dependency from the release @@ -175,7 +229,8 @@ externalRabbitmq: ### Connect external Redis To use an external Redis instance set redis.enabled to `false` and configure the `externalRedis` parameters. -``` + +```yaml redis: enabled: false # Disable the Redis dependency from the release @@ -185,7 +240,8 @@ externalRedis: ``` ## Update -```shell + +```bash # Add & upgrade the repository helm repo add grafana https://grafana.github.io/helm-charts helm repo update @@ -203,19 +259,23 @@ helm upgrade \ After re-deploying, please also update the Grafana OnCall plugin on the plugin version page. See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin-management/#update-a-plugin) for more info on updating Grafana plugins. ## Uninstall + ### Uninstalling the helm chart + ```bash helm delete release-oncall ``` ### Clean up PVC's + ```bash kubectl delete pvc data-release-oncall-mariadb-0 data-release-oncall-rabbitmq-0 \ redis-data-release-oncall-redis-master-0 redis-data-release-oncall-redis-replicas-0 \ redis-data-release-oncall-redis-replicas-1 redis-data-release-oncall-redis-replicas-2 ``` - + ### Clean up secrets + ```bash kubectl delete secrets certificate-tls release-oncall-cert-manager-webhook-ca release-oncall-ingress-nginx-admission ``` diff --git a/helm/oncall/charts/postgresql-11.9.10.tgz b/helm/oncall/charts/postgresql-11.9.10.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2e267653efb19bc5fdf9a5e61ea4e4d4a7f1d91d GIT binary patch literal 57385 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
    hGl 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 Date: Wed, 2 Nov 2022 11:32:05 +0200 Subject: [PATCH 34/50] =?UTF-8?q?Add=20ability=20to=20set=20more=20env=20v?= =?UTF-8?q?ars=20via=20config=20&=20change=20env=20template=20&=20f?= =?UTF-8?q?=E2=80=A6=20(#674)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ability to set more env vars via config & change env template & fix env spacing * Fix default redirectHost value * Additional Twilio env vars + modified Twilio vars flow * Fix wrong phoneNumber rendering Co-authored-by: Ildar Iskhakov --- helm/oncall/templates/_env.tpl | 43 ++++++++++++++++---- helm/oncall/templates/_helpers.tpl | 18 +++++--- helm/oncall/templates/celery/_deployment.tpl | 22 ++++++---- helm/oncall/templates/engine/deployment.yaml | 23 +++++++---- helm/oncall/values.yaml | 18 +++++++- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/helm/oncall/templates/_env.tpl b/helm/oncall/templates/_env.tpl index 93fbc7d8..f8c6fe51 100644 --- a/helm/oncall/templates/_env.tpl +++ b/helm/oncall/templates/_env.tpl @@ -21,7 +21,7 @@ value: "True" - name: UWSGI_LISTEN value: "1024" -{{- end }} +{{- end -}} {{- define "snippet.oncall.slack.env" -}} {{- if .Values.oncall.slack.enabled -}} @@ -36,12 +36,12 @@ - name: SLACK_SIGNING_SECRET value: {{ .Values.oncall.slack.signingSecret | default "" | quote }} - name: SLACK_INSTALL_RETURN_REDIRECT_HOST - value: "https://{{ .Values.base_url }}" + value: {{ .Values.oncall.slack.redirectHost | default (printf "https://%s" .Values.base_url) | quote }} {{- else -}} - name: FEATURE_SLACK_INTEGRATION_ENABLED value: {{ .Values.oncall.slack.enabled | toString | title | quote }} {{- end -}} -{{- end }} +{{- end -}} {{- define "snippet.oncall.telegram.env" -}} {{- if .Values.oncall.telegram.enabled -}} @@ -55,7 +55,36 @@ - name: FEATURE_TELEGRAM_INTEGRATION_ENABLED value: {{ .Values.oncall.telegram.enabled | toString | title | quote }} {{- end -}} -{{- end }} +{{- end -}} + +{{- define "snippet.oncall.twilio.env" -}} +{{- with .Values.oncall.twilio -}} +{{- if .accountSid }} +- name: TWILIO_ACCOUNT_SID + value: {{ .accountSid | quote }} +{{- end -}} +{{- if .authToken }} +- name: TWILIO_AUTH_TOKEN + value: {{ .authToken | quote }} +{{- end -}} +{{- if .phoneNumber }} +- name: TWILIO_NUMBER + value: {{ .phoneNumber | quote }} +{{- end -}} +{{- if .verifySid }} +- name: TWILIO_VERIFY_SERVICE_SID + value: {{ .verifySid | quote }} +{{- end -}} +{{- if .apiKeySid }} +- name: TWILIO_API_KEY_SID + value: {{ .apiKeySid | quote }} +{{- end -}} +{{- if .apiKeySecret }} +- name: TWILIO_API_KEY_SECRET + value: {{ .apiKeySecret | quote }} +{{- end -}} +{{- end -}} +{{- end -}} {{- define "snippet.celery.env" -}} {{- if .Values.celery.worker_queue }} @@ -78,7 +107,7 @@ - name: CELERY_WORKER_SHUTDOWN_INTERVAL value: {{ .Values.celery.worker_shutdown_interval }} {{- end -}} -{{- end }} +{{- end -}} {{- define "snippet.mysql.env" -}} - name: MYSQL_HOST @@ -130,7 +159,7 @@ {{- define "snippet.mysql.user" -}} {{- if and (not .Values.mariadb.enabled) .Values.externalMysql.user -}} -{{- .Values.externalMysql.user | quote}} +{{- .Values.externalMysql.user | quote }} {{- else -}} "root" {{- end -}} @@ -220,7 +249,7 @@ value: {{ include "snippet.rabbitmq.protocol" . }} - name: RABBITMQ_VHOST value: {{ include "snippet.rabbitmq.vhost" . }} -{{- end }} +{{- end -}} {{- define "snippet.rabbitmq.user" -}} {{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.user -}} diff --git a/helm/oncall/templates/_helpers.tpl b/helm/oncall/templates/_helpers.tpl index b5f4dbcc..110bc525 100644 --- a/helm/oncall/templates/_helpers.tpl +++ b/helm/oncall/templates/_helpers.tpl @@ -93,12 +93,20 @@ Create the name of the service account to use securityContext: {{ toYaml .Values.init.securityContext| nindent 4}} env: - {{- include "snippet.oncall.env" . | nindent 12 }} - {{- include "snippet.mysql.env" . | nindent 12 }} - {{- include "snippet.rabbitmq.env" . | nindent 12 }} - {{- include "snippet.redis.env" . | nindent 12 }} + {{- include "snippet.oncall.env" . | nindent 4 }} + {{- include "snippet.mysql.env" . | nindent 4 }} + {{- include "snippet.rabbitmq.env" . | nindent 4 }} + {{- include "snippet.redis.env" . | nindent 4 }} {{- if .Values.env }} - {{- toYaml .Values.env | nindent 12 }} + {{- if (kindIs "map" .Values.env) }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value }} + {{- end -}} + {{/* support previous schema */}} + {{- else }} + {{- toYaml .Values.env | nindent 4 }} + {{- end }} {{- end }} {{- end }} diff --git a/helm/oncall/templates/celery/_deployment.tpl b/helm/oncall/templates/celery/_deployment.tpl index e25d4adc..4e09cb30 100644 --- a/helm/oncall/templates/celery/_deployment.tpl +++ b/helm/oncall/templates/celery/_deployment.tpl @@ -28,12 +28,12 @@ spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} initContainers: - {{- if eq .Values.database.type "mysql" }} - {{- include "oncall.mariadb.wait-for-db" . | indent 8 }} - {{- end }} - {{- if eq .Values.database.type "postgresql" }} - {{- include "oncall.postgresql.wait-for-db" . | indent 8 }} - {{- end }} + {{- if eq .Values.database.type "mysql" }} + {{- include "oncall.mariadb.wait-for-db" . | indent 8 }} + {{- end }} + {{- if eq .Values.database.type "postgresql" }} + {{- include "oncall.postgresql.wait-for-db" . | indent 8 }} + {{- end }} containers: - name: {{ .Chart.Name }} securityContext: @@ -56,7 +56,15 @@ spec: {{- include "snippet.rabbitmq.env" . | nindent 12 }} {{- include "snippet.redis.env" . | nindent 12 }} {{- if .Values.env }} - {{- toYaml .Values.env | nindent 12 }} + {{- if (kindIs "map" .Values.env) }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value }} + {{- end -}} + {{/* support previous schema */}} + {{- else }} + {{- toYaml .Values.env | nindent 12 }} + {{- end }} {{- end }} {{- if .Values.celery.livenessProbe.enabled }} livenessProbe: diff --git a/helm/oncall/templates/engine/deployment.yaml b/helm/oncall/templates/engine/deployment.yaml index b06462e4..640bb84b 100644 --- a/helm/oncall/templates/engine/deployment.yaml +++ b/helm/oncall/templates/engine/deployment.yaml @@ -32,12 +32,12 @@ spec: securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} initContainers: - {{- if eq .Values.database.type "mysql" }} - {{- include "oncall.mariadb.wait-for-db" . | indent 8 }} - {{- end }} - {{- if eq .Values.database.type "postgresql" }} - {{- include "oncall.postgresql.wait-for-db" . | indent 8 }} - {{- end }} + {{- if eq .Values.database.type "mysql" }} + {{- include "oncall.mariadb.wait-for-db" . | indent 8 }} + {{- end }} + {{- if eq .Values.database.type "postgresql" }} + {{- include "oncall.postgresql.wait-for-db" . | indent 8 }} + {{- end }} containers: - name: {{ .Chart.Name }} securityContext: @@ -53,6 +53,7 @@ spec: {{- include "snippet.oncall.slack.env" . | nindent 12 }} {{- include "snippet.oncall.telegram.env" . | nindent 12 }} {{- include "snippet.oncall.smtp.env" . | nindent 12 }} + {{- include "snippet.oncall.twilio.env" . | nindent 12 }} {{- if eq .Values.database.type "mysql" }} {{- include "snippet.mysql.env" . | nindent 12 }} {{- end }} @@ -62,7 +63,15 @@ spec: {{- include "snippet.rabbitmq.env" . | nindent 12 }} {{- include "snippet.redis.env" . | nindent 12 }} {{- if .Values.env }} - {{- toYaml .Values.env | nindent 12 }} + {{- if (kindIs "map" .Values.env) }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value }} + {{- end -}} + {{/* support previous schema */}} + {{- else }} + {{- toYaml .Values.env | nindent 12 }} + {{- end }} {{- end }} livenessProbe: httpGet: diff --git a/helm/oncall/values.yaml b/helm/oncall/values.yaml index 5939a9e1..eff39909 100644 --- a/helm/oncall/values.yaml +++ b/helm/oncall/values.yaml @@ -84,6 +84,8 @@ oncall: # requests comming from Slack. # api.slack.com/apps/ -> Basic Information -> App Credentials -> Signing Secret signingSecret: ~ + # OnCall external URL + redirectHost: ~ telegram: enabled: false token: ~ @@ -96,13 +98,27 @@ oncall: password: ~ tls: ~ fromEmail: ~ + twilio: + # Twilio account SID/username to allow OnCall to send SMSes and make phone calls + accountSid: "" + # Twilio password to allow OnCall to send SMSes and make calls + authToken: "" + # Number from which you will receive calls and SMS (NOTE: must be quoted, otherwise would be rendered as float value) + phoneNumber: "" + # SID of Twilio service for number verification. You can create a service in Twilio web interface. + # twilio.com -> verify -> create new service + verifySid: "" + # Twilio API key SID/username to allow OnCall to send SMSes and make phone calls + apiKeySid: "" + # Twilio API key secret/password to allow OnCall to send SMSes and make phone calls + apiKeySecret: "" # Whether to run django database migrations automatically migrate: enabled: true # Additional env variables to add to deployments -env: [] +env: {} # Enable ingress object for external access to the resources ingress: From ed5262f2f5931645d8e69c219f519d1be644c943 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Wed, 2 Nov 2022 17:37:41 +0800 Subject: [PATCH 35/50] Add BROKER_TYPE env var to the helm chart (#697) --- helm/oncall/templates/_env.tpl | 8 ++++++-- helm/oncall/templates/secrets.yaml | 2 +- helm/oncall/values.yaml | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/helm/oncall/templates/_env.tpl b/helm/oncall/templates/_env.tpl index f8c6fe51..89d1a52c 100644 --- a/helm/oncall/templates/_env.tpl +++ b/helm/oncall/templates/_env.tpl @@ -21,6 +21,8 @@ value: "True" - name: UWSGI_LISTEN value: "1024" +- name: BROKER_TYPE + value: {{ .Values.broker.type | default "rabbitmq" }} {{- end -}} {{- define "snippet.oncall.slack.env" -}} @@ -150,8 +152,8 @@ {{- end -}} {{- define "snippet.mysql.db" -}} -{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.db -}} -{{- required "externalMysql.db is required if not mariadb.enabled" .Values.externalMysql.db | quote}} +{{- if and (not .Values.mariadb.enabled) .Values.externalMysql.db_name -}} +{{- required "externalMysql.db is required if not mariadb.enabled" .Values.externalMysql.db_name | quote}} {{- else -}} "oncall" {{- end -}} @@ -234,6 +236,7 @@ {{- end -}} {{- define "snippet.rabbitmq.env" -}} +{{- if eq .Values.broker.type "rabbitmq" -}} - name: RABBITMQ_USERNAME value: {{ include "snippet.rabbitmq.user" . }} - name: RABBITMQ_PASSWORD @@ -249,6 +252,7 @@ value: {{ include "snippet.rabbitmq.protocol" . }} - name: RABBITMQ_VHOST value: {{ include "snippet.rabbitmq.vhost" . }} +{{- end }} {{- end -}} {{- define "snippet.rabbitmq.user" -}} diff --git a/helm/oncall/templates/secrets.yaml b/helm/oncall/templates/secrets.yaml index 39e48a00..0997c93d 100644 --- a/helm/oncall/templates/secrets.yaml +++ b/helm/oncall/templates/secrets.yaml @@ -21,7 +21,7 @@ data: mariadb-root-password: {{ required "externalMysql.password is required if not mariadb.enabled" .Values.externalMysql.password | b64enc | quote }} {{- end }} --- -{{ if not .Values.rabbitmq.enabled -}} +{{ if and (eq .Values.broker.type "rabbitmq") (not .Values.rabbitmq.enabled) -}} apiVersion: v1 kind: Secret metadata: diff --git a/helm/oncall/values.yaml b/helm/oncall/values.yaml index eff39909..5c30878d 100644 --- a/helm/oncall/values.yaml +++ b/helm/oncall/values.yaml @@ -229,6 +229,9 @@ externalPostgresql: rabbitmq: enabled: true +broker: + type: rabbitmq + externalRabbitmq: host: port: From 12da061d6efb80b9a9da2581bcbca1f160ad676f Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Wed, 2 Nov 2022 17:40:33 +0800 Subject: [PATCH 36/50] Update Chart.yaml --- helm/oncall/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/oncall/Chart.yaml b/helm/oncall/Chart.yaml index 55564328..fd0483fd 100644 --- a/helm/oncall/Chart.yaml +++ b/helm/oncall/Chart.yaml @@ -8,13 +8,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.6 +version: 1.0.7 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v1.0.40" +appVersion: "v1.0.49" dependencies: - name: cert-manager version: v1.8.0 From 7dce3196bea6e285d2fc5ba86d4eadb487c35a91 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 2 Nov 2022 11:08:58 +0000 Subject: [PATCH 37/50] fix live settings caching columns render --- grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx b/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx index 7028e366..db5cd138 100644 --- a/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx +++ b/grafana-plugin/src/pages/livesettings/LiveSettingsPage.tsx @@ -89,13 +89,13 @@ class LiveSettings extends React.Component { width: '20%', title: 'Value', - render: this.renderValue, + render: (item: GlobalSetting) => this.renderValue(item), // to avoid caching previous render result key: 'value', }, { width: '20%', title: 'ENV or default', - render: this.renderDefault, + render: (item: GlobalSetting) => this.renderDefault(item), // to avoid caching previous render result key: 'default', }, { From 0c78d25529f2b5fcb4afbda82a53d85f0f0baca6 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 2 Nov 2022 11:44:42 +0000 Subject: [PATCH 38/50] add selector-max-type stylelint rule --- grafana-plugin/.stylelintrc | 4 ++++ grafana-plugin/package.json | 4 ++-- .../ScheduleBorderedAvatar.module.scss | 6 ++++-- .../src/components/SourceCode/SourceCode.module.scss | 9 +++++---- grafana-plugin/src/components/Text/Text.module.scss | 9 +++++++++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/grafana-plugin/.stylelintrc b/grafana-plugin/.stylelintrc index c37d4c08..31c2b181 100644 --- a/grafana-plugin/.stylelintrc +++ b/grafana-plugin/.stylelintrc @@ -3,6 +3,10 @@ "plugins": ["stylelint-prettier"], "rules": { "block-no-empty": [true, { "severity": "warning" }], + "selector-max-type": [ 0, { + "severity": "error", + "ignore": ["child", "compounded", "descendant", "next-sibling"] + }], "selector-pseudo-class-no-unknown": [ true, { diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index 9398e5c9..ea95dd57 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -5,8 +5,8 @@ "scripts": { "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 ./src", "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 --quiet ./src", - "stylelint": "stylelint ./src/**/*.css", - "stylelint:fix": "stylelint --fix ./src/**/*.css", + "stylelint": "stylelint ./src/**/*.{css,scss,module.css,module.scss}", + "stylelint:fix": "stylelint --fix ./src/**/*.{css,scss,module.css,module.scss}", "build": "grafana-toolkit plugin:build", "test": "jest --verbose", "dev": "grafana-toolkit plugin:dev", diff --git a/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.module.scss b/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.module.scss index 0e179253..d65587a4 100644 --- a/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.module.scss +++ b/grafana-plugin/src/components/ScheduleBorderedAvatar/ScheduleBorderedAvatar.module.scss @@ -1,12 +1,14 @@ .root { position: relative; } + .avatar { position: absolute; - top: 0px; - left: 0px; + top: 0; + left: 0; z-index: -1; } + .icon { position: relative; top: -8px; diff --git a/grafana-plugin/src/components/SourceCode/SourceCode.module.scss b/grafana-plugin/src/components/SourceCode/SourceCode.module.scss index ec139080..76c51d24 100644 --- a/grafana-plugin/src/components/SourceCode/SourceCode.module.scss +++ b/grafana-plugin/src/components/SourceCode/SourceCode.module.scss @@ -1,10 +1,6 @@ .root { position: relative; width: 100%; - - &:hover .copyButton { - opacity: 1; - } } .scroller { @@ -24,6 +20,11 @@ right: 15px; transition: opacity 0.2s ease; } + .copyButton { opacity: 0; } + +.root:hover .copyButton { + opacity: 1; +} diff --git a/grafana-plugin/src/components/Text/Text.module.scss b/grafana-plugin/src/components/Text/Text.module.scss index 158155bb..618bb298 100644 --- a/grafana-plugin/src/components/Text/Text.module.scss +++ b/grafana-plugin/src/components/Text/Text.module.scss @@ -6,30 +6,39 @@ &--primary { color: var(--primary-text-color); } + &--secondary { color: var(--secondary-text-color); } + &--disabled { color: var(--disabled-text-color); } + &--warning { color: var(--warning-text-color); } + &--link { color: var(--primary-text-link); } + &--success { color: var(--green-5); } + &--strong { font-weight: bold; } + &--underline { text-decoration: underline; } + &--small { font-size: 12px; } + &--large { font-size: 20px; } From 85d94d342e93f44068069112f1d3b8faddf9825d Mon Sep 17 00:00:00 2001 From: Matvey Kukuy Date: Wed, 2 Nov 2022 16:58:47 +0200 Subject: [PATCH 39/50] Readme about how to add Integrations and Zabbix Integration (#653) * Readme and zabbix * Typos * Linking --- DEVELOPER.md | 2 + README.md | 1 + engine/config_integrations/README.md | 21 ++++++++++ engine/config_integrations/zabbix.py | 62 ++++++++++++++++++++++++++++ engine/settings/base.py | 1 + 5 files changed, 87 insertions(+) create mode 100644 engine/config_integrations/README.md create mode 100644 engine/config_integrations/zabbix.py diff --git a/DEVELOPER.md b/DEVELOPER.md index e65f186a..f85cf648 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -20,6 +20,8 @@ ## Developer quickstart +Related: [How to develop integrations](/engine/config_integrations/README.md) + ### Code style - [isort](https://github.com/PyCQA/isort), [black](https://github.com/psf/black) and [flake8](https://github.com/PyCQA/flake8) are used to format backend code diff --git a/README.md b/README.md index 08019328..96455f6e 100644 --- a/README.md +++ b/README.md @@ -87,5 +87,6 @@ See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin - _Migration from the PagerDuty_ - [Migrator](https://github.com/grafana/oncall/tree/dev/tools/pagerduty-migrator) - _Documentation_ - [Grafana OnCall](https://grafana.com/docs/grafana-cloud/oncall/) +- _How To Add Integration_ - [How to Add Integration](https://github.com/grafana/oncall/tree/dev/engine/config_integrations/README.md) - _Blog Post_ - [Announcing Grafana OnCall, the easiest way to do on-call management](https://grafana.com/blog/2021/11/09/announcing-grafana-oncall/) - _Presentation_ - [Deep dive into the Grafana, Prometheus, and Alertmanager stack for alerting and on-call management](https://grafana.com/go/observabilitycon/2021/alerting/?pg=blog) diff --git a/engine/config_integrations/README.md b/engine/config_integrations/README.md new file mode 100644 index 00000000..13c7189d --- /dev/null +++ b/engine/config_integrations/README.md @@ -0,0 +1,21 @@ +# Contribute the new Integration to OnCall + +Related: [DEVELOPER.md](/DEVELOPER.md) + +"Integration" in OnCall is a pre-configured webhook for alert consumption from alert sources. Usually, alert sources are monitoring systems such as Grafana or Zabbix. + +Integration is a set of "templates" which are dumped from the integration config once the integration is created. Further changes to "templates" don't reflect on the integration config. Read more about templates [here](https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/). + +This instruction is supposed to help you to build templates to integrate OnCall with a new source of alerts. If you don't want to contribute to OnCall and are looking for a help integrating with custom alert source as a user, refer to [this](https://grafana.com/docs/oncall/latest/integrations/create-custom-templates/) instruction. + +# Files related to Integrations +0. Refer to "Grafana" integration as the most complete example. +1. Each integration should have a `{{integration_name_in_snake_case}}.py` file in `/engine/config_integrations`. There you'll find Templates that will be copied to the Integration Templates once the integration is created by the user in the OnCall UI; Example Payload; and Tests which should match the result of the rendering of Example Payload as using Templates. The best way to build such a file is to create Webhook Integration, write & debug templates in the UI first and copy-paste them to the file after. +2. Each integration should be listed in the `/engine/settings/base.py` file, section `INSTALLED_ONCALL_INTEGRATIONS`. +3. Each integration should have "How to connect" instruction stored as `integration_{{integration_name_in_snake_case}}.html` in the `engine/apps/integrations/html` folder. `.py` file has a `slug` field that is used to locate `.html` file. + +# What do we expect from high-quality integration? + +1. User-friendly integration instruction. +2. Proper grouping following source's logics. If source generates multiple alerts per "detection" it would be nice to provide suitable grouping & resolving configuration in the templates. +3. Awesome rendering. We all love when alerts look good in Slack, SMS and all other rendering destinations. diff --git a/engine/config_integrations/zabbix.py b/engine/config_integrations/zabbix.py new file mode 100644 index 00000000..63b3e3b5 --- /dev/null +++ b/engine/config_integrations/zabbix.py @@ -0,0 +1,62 @@ +# Main +enabled = True +title = "Zabbix" +slug = "zabbix" +short_description = None +description = None +is_displayed_on_web = True +is_featured = False +is_able_to_autoresolve = True +is_demo_alert_enabled = True + +description = None + +# Default templates +slack_title = """\ +*<{{ grafana_oncall_link }}|#{{ grafana_oncall_incident_id }} {{ payload.get("title", "Title undefined (check Slack Title Template)") }}>* via {{ integration_name }} +{% if source_link %} + (*<{{ source_link }}|source>*) +{%- endif %}""" + +slack_message = '{{ payload.get("message", "") }}' + +slack_image_url = '{{ payload.get("image_url", "") }}' + +web_title = '{{ payload.get("title", "Title undefined (check Web Title Template)") }}' + +web_message = slack_message + +web_image_url = slack_image_url + +sms_title = web_title + +phone_call_title = sms_title + +email_title = web_title + +email_message = web_message + +telegram_title = sms_title + +telegram_message = slack_message + +telegram_image_url = slack_image_url + +source_link = "{{ payload.link_to_upstream_details }}" + +grouping_id = '{{ payload.get("alert_uid", "")}}' + +resolve_condition = '{{ payload.get("state", "").upper() == "OK" }}' + +acknowledge_condition = None + +group_verbose_name = web_title + +example_payload = { + "alert_uid": "08d6891a-835c-e661-39fa-96b6a9e26552", + "title": "TestAlert: The whole system is down", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/e/ee/Grumpy_Cat_by_Gage_Skidmore.jpg", + "state": "alerting", + "link_to_upstream_details": "https://en.wikipedia.org/wiki/Downtime", + "message": "This alert was sent by user for the demonstration purposes\nSmth happened. Oh no!", +} diff --git a/engine/settings/base.py b/engine/settings/base.py index e701ea24..b95a389c 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -586,6 +586,7 @@ INSTALLED_ONCALL_INTEGRATIONS = [ "config_integrations.maintenance", "config_integrations.manual", "config_integrations.slack_channel", + "config_integrations.zabbix", ] if OSS_INSTALLATION: From c2546460f369e6817c77d539a9359acd3b8ebc2c Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Wed, 2 Nov 2022 10:36:12 -0600 Subject: [PATCH 40/50] Add user and org id to log to make troubleshooting easier --- engine/engine/middlewares.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/engine/engine/middlewares.py b/engine/engine/middlewares.py index 90c1e130..367bf69c 100644 --- a/engine/engine/middlewares.py +++ b/engine/engine/middlewares.py @@ -23,19 +23,21 @@ class RequestTimeLoggingMiddleware(MiddlewareMixin): seconds = (dt - request._logging_start_dt).total_seconds() status_code = 0 if response is None else response.status_code content_length = request.headers.get("content-length", default=0) - integration_type = "N/A" - integration_token = "N/A" + message = ( + "inbound " + f"latency={str(seconds)} status={status_code} method={request.method} path={request.path} " + f"content-length={content_length} slow={int(seconds > settings.SLOW_THRESHOLD_SECONDS)} " + ) + if request.user and request.user.id: + user_id = request.user.id + org_id = request.user.organization_id + message += f"user_id={user_id} org_id={org_id} " if request.path.startswith("/integrations/v1"): split_path = request.path.split("/") integration_type = split_path[3] integration_token = split_path[4] - logging.info( - "inbound " - f"latency={str(seconds)} status={status_code} method={request.method} path={request.path} " - f"content-length={content_length} slow={int(seconds > settings.SLOW_THRESHOLD_SECONDS)} " - f"integration_type={integration_type} " - f"integration_token={integration_token}" - ) + message += f"integration_type={integration_type} integration_token={integration_token} " + logging.info(message) def process_request(self, request): self.log_message(request, None, "request") From 538e28e739e612587f1775d3742ea371c3e59102 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Wed, 2 Nov 2022 10:47:26 -0600 Subject: [PATCH 41/50] Check user attribute --- engine/engine/middlewares.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/engine/middlewares.py b/engine/engine/middlewares.py index 367bf69c..cf5878ad 100644 --- a/engine/engine/middlewares.py +++ b/engine/engine/middlewares.py @@ -28,7 +28,7 @@ class RequestTimeLoggingMiddleware(MiddlewareMixin): f"latency={str(seconds)} status={status_code} method={request.method} path={request.path} " f"content-length={content_length} slow={int(seconds > settings.SLOW_THRESHOLD_SECONDS)} " ) - if request.user and request.user.id: + if hasattr(request, "user") and request.user and request.user.id: user_id = request.user.id org_id = request.user.organization_id message += f"user_id={user_id} org_id={org_id} " From 3e2d52ad517db90312527ae36d43fbc43a4364b6 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Mon, 31 Oct 2022 14:10:26 -0300 Subject: [PATCH 42/50] Update web schedule events to return all-day as datetimes --- engine/apps/api/views/schedule.py | 8 ++++- .../apps/schedules/models/on_call_schedule.py | 28 +++++++++++---- .../schedules/tests/test_on_call_schedule.py | 36 +++++++++++++++---- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/engine/apps/api/views/schedule.py b/engine/apps/api/views/schedule.py index 04de4393..7ecebaf4 100644 --- a/engine/apps/api/views/schedule.py +++ b/engine/apps/api/views/schedule.py @@ -264,7 +264,13 @@ class ScheduleView( if filter_by is not None and filter_by != EVENTS_FILTER_BY_FINAL: filter_by = OnCallSchedule.PRIMARY if filter_by == EVENTS_FILTER_BY_ROTATION else OnCallSchedule.OVERRIDES events = schedule.filter_events( - user_tz, starting_date, days=days, with_empty=True, with_gap=resolve_schedule, filter_by=filter_by + user_tz, + starting_date, + days=days, + with_empty=True, + with_gap=resolve_schedule, + filter_by=filter_by, + all_day_datetime=True, ) else: # return final schedule events = schedule.final_events(user_tz, starting_date, days) diff --git a/engine/apps/schedules/models/on_call_schedule.py b/engine/apps/schedules/models/on_call_schedule.py index 24aa8d0d..e2287ecd 100644 --- a/engine/apps/schedules/models/on_call_schedule.py +++ b/engine/apps/schedules/models/on_call_schedule.py @@ -202,7 +202,16 @@ class OnCallSchedule(PolymorphicModel): """Return public primary keys for all users referenced in the schedule.""" return set() - def filter_events(self, user_timezone, starting_date, days, with_empty=False, with_gap=False, filter_by=None): + def filter_events( + self, + user_timezone, + starting_date, + days, + with_empty=False, + with_gap=False, + filter_by=None, + all_day_datetime=False, + ): """Return filtered events from schedule.""" shifts = ( list_of_oncall_shifts_from_ical( @@ -212,13 +221,18 @@ class OnCallSchedule(PolymorphicModel): ) events = [] for shift in shifts: - all_day = type(shift["start"]) == datetime.date + start = shift["start"] + all_day = type(start) == datetime.date + # fix confusing end date for all-day event + end = shift["end"] - timezone.timedelta(days=1) if all_day else shift["end"] + if all_day and all_day_datetime: + start = datetime.datetime.combine(start, datetime.datetime.min.time(), tzinfo=pytz.UTC) + end = datetime.datetime.combine(start, datetime.datetime.max.time(), tzinfo=pytz.UTC) is_gap = shift.get("is_gap", False) shift_json = { "all_day": all_day, - "start": shift["start"], - # fix confusing end date for all-day event - "end": shift["end"] - timezone.timedelta(days=1) if all_day else shift["end"], + "start": start, + "end": end, "users": [ { "display_name": user.username, @@ -246,7 +260,9 @@ class OnCallSchedule(PolymorphicModel): def final_events(self, user_tz, starting_date, days): """Return schedule final events, after resolving shifts and overrides.""" - events = self.filter_events(user_tz, starting_date, days=days, with_empty=True, with_gap=True) + events = self.filter_events( + user_tz, starting_date, days=days, with_empty=True, with_gap=True, all_day_datetime=True + ) events = self._resolve_schedule(events) return events diff --git a/engine/apps/schedules/tests/test_on_call_schedule.py b/engine/apps/schedules/tests/test_on_call_schedule.py index 8bb079f4..93a2c8ff 100644 --- a/engine/apps/schedules/tests/test_on_call_schedule.py +++ b/engine/apps/schedules/tests/test_on_call_schedule.py @@ -246,14 +246,38 @@ def test_filter_events_ical_all_day(make_organization, make_user_for_organizatio events = schedule.final_events("UTC", start_date, days=2) expected_events = [ - # all_day, users, start - (False, ["@Bernard Desruisseaux"], datetime.datetime(2021, 1, 26, 8, 0, tzinfo=pytz.UTC)), - (True, ["@Alex"], datetime.date(2021, 1, 27)), - (False, ["@Bob"], datetime.datetime(2021, 1, 27, 8, 0, tzinfo=pytz.UTC)), + # all_day, users, start, end + ( + False, + ["@Bernard Desruisseaux"], + datetime.datetime(2021, 1, 26, 8, 0, tzinfo=pytz.UTC), + datetime.datetime(2021, 1, 26, 17, 0, tzinfo=pytz.UTC), + ), + ( + True, + ["@Alex"], + datetime.datetime(2021, 1, 27, 0, 0, tzinfo=pytz.UTC), + datetime.datetime(2021, 1, 27, 23, 59, 59, 999999, tzinfo=pytz.UTC), + ), + ( + False, + ["@Bob"], + datetime.datetime(2021, 1, 27, 8, 0, tzinfo=pytz.UTC), + datetime.datetime(2021, 1, 27, 17, 0, tzinfo=pytz.UTC), + ), + ] + expected = [ + {"all_day": all_day, "users": users, "start": start, "end": end} + for all_day, users, start, end in expected_events ] - expected = [{"all_day": all_day, "users": users, "start": start} for all_day, users, start in expected_events] returned = [ - {"all_day": e["all_day"], "users": [u["display_name"] for u in e["users"]], "start": e["start"]} for e in events + { + "all_day": e["all_day"], + "users": [u["display_name"] for u in e["users"]], + "start": e["start"], + "end": e["end"], + } + for e in events ] assert returned == expected From 5b29ab92ead9786015c99e87652add8b442eafa0 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Wed, 2 Nov 2022 16:10:41 -0600 Subject: [PATCH 43/50] Migration for regions and organizations --- .../migrations/0004_auto_20221025_0316.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 engine/apps/user_management/migrations/0004_auto_20221025_0316.py diff --git a/engine/apps/user_management/migrations/0004_auto_20221025_0316.py b/engine/apps/user_management/migrations/0004_auto_20221025_0316.py new file mode 100644 index 00000000..8d1b15c8 --- /dev/null +++ b/engine/apps/user_management/migrations/0004_auto_20221025_0316.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.15 on 2022-10-25 03:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0003_user_hide_phone_number'), + ] + + operations = [ + migrations.CreateModel( + name='Region', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=300)), + ('slug', models.CharField(max_length=50, unique=True)), + ('oncall_backend_url', models.URLField(null=True)), + ], + ), + migrations.AddField( + model_name='organization', + name='region_slug', + field=models.CharField(default=None, max_length=300, null=True), + ), + migrations.AddField( + model_name='organization', + name='migration_destination', + field=models.ForeignKey(db_column='migration_destination_slug', default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='regions', to='user_management.region', to_field='slug'), + ), + ] From c01068898a3e2a7a2b5cd038506354e860b1aabb Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Thu, 3 Nov 2022 08:31:00 +0100 Subject: [PATCH 44/50] Helm: allow for using existing secret for RabbitMQ (#761) * init rabbitmq existing secret Signed-off-by: David van der Spek * bump chart Signed-off-by: David van der Spek Signed-off-by: David van der Spek --- helm/oncall/Chart.yaml | 2 +- helm/oncall/README.md | 5 +++++ helm/oncall/templates/_env.tpl | 20 +++++++++++++++++++- helm/oncall/templates/secrets.yaml | 4 ++-- helm/oncall/values.yaml | 6 ++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/helm/oncall/Chart.yaml b/helm/oncall/Chart.yaml index fd0483fd..b1b7e18a 100644 --- a/helm/oncall/Chart.yaml +++ b/helm/oncall/Chart.yaml @@ -8,7 +8,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.7 +version: 1.0.8 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/helm/oncall/README.md b/helm/oncall/README.md index d64e6c89..92fcd911 100644 --- a/helm/oncall/README.md +++ b/helm/oncall/README.md @@ -224,6 +224,11 @@ externalRabbitmq: port: user: password: + protocol: + vhost: + existingSecret: "" + passwordKey: password + usernameKey: username ``` ### Connect external Redis diff --git a/helm/oncall/templates/_env.tpl b/helm/oncall/templates/_env.tpl index 89d1a52c..855ce711 100644 --- a/helm/oncall/templates/_env.tpl +++ b/helm/oncall/templates/_env.tpl @@ -237,13 +237,21 @@ {{- define "snippet.rabbitmq.env" -}} {{- if eq .Values.broker.type "rabbitmq" -}} +{{- if and (not .Values.rabbitmq.enabled) (not .Values.externalRabbitmq.existingSecret) (not .Values.externalRabbitmq.usernameKey) .Values.externalRabbitmq.user }} - name: RABBITMQ_USERNAME value: {{ include "snippet.rabbitmq.user" . }} +{{- else if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.existingSecret .Values.externalRabbitmq.usernameKey (not .Values.externalRabbitmq.user) }} +- name: RABBITMQ_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "snippet.rabbitmq.password.secret.name" . }} + key: {{ .Values.externalRabbitmq.usernameKey }} +{{- end }} - name: RABBITMQ_PASSWORD valueFrom: secretKeyRef: name: {{ include "snippet.rabbitmq.password.secret.name" . }} - key: rabbitmq-password + key: {{ include "snippet.rabbitmq.password.secret.key" . }} - name: RABBITMQ_HOST value: {{ include "snippet.rabbitmq.host" . }} - name: RABBITMQ_PORT @@ -298,11 +306,21 @@ {{- define "snippet.rabbitmq.password.secret.name" -}} {{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.password -}} {{ include "oncall.fullname" . }}-rabbitmq-external +{{- else if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.existingSecret -}} +{{ .Values.externalRabbitmq.existingSecret }} {{- else -}} {{ include "oncall.rabbitmq.fullname" . }} {{- end -}} {{- end -}} +{{- define "snippet.rabbitmq.password.secret.key" -}} +{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.passwordKey -}} +{{ .Values.externalRabbitmq.passwordKey }} +{{- else -}} +rabbitmq-password +{{- end -}} +{{- end -}} + {{- define "snippet.redis.host" -}} {{- if and (not .Values.redis.enabled) .Values.externalRedis.host -}} {{- required "externalRedis.host is required if not redis.enabled" .Values.externalRedis.host | quote }} diff --git a/helm/oncall/templates/secrets.yaml b/helm/oncall/templates/secrets.yaml index 0997c93d..dfd7cdb2 100644 --- a/helm/oncall/templates/secrets.yaml +++ b/helm/oncall/templates/secrets.yaml @@ -21,14 +21,14 @@ data: mariadb-root-password: {{ required "externalMysql.password is required if not mariadb.enabled" .Values.externalMysql.password | b64enc | quote }} {{- end }} --- -{{ if and (eq .Values.broker.type "rabbitmq") (not .Values.rabbitmq.enabled) -}} +{{ if and (eq .Values.broker.type "rabbitmq") (not .Values.rabbitmq.enabled) (not .Values.externalRabbitmq.existingSecret) -}} apiVersion: v1 kind: Secret metadata: name: {{ include "oncall.fullname" . }}-rabbitmq-external type: Opaque data: - rabbitmq-password: {{ required "externalRabbitmq.password is required if not rabbitmq.enabled" .Values.externalRabbitmq.password | b64enc | quote }} + rabbitmq-password: {{ required "externalRabbitmq.password is required if not rabbitmq.enabled and not externalRabbitmq.existingSecret" .Values.externalRabbitmq.password | b64enc | quote }} {{- end }} --- {{ if not .Values.redis.enabled -}} diff --git a/helm/oncall/values.yaml b/helm/oncall/values.yaml index 5c30878d..3de0e122 100644 --- a/helm/oncall/values.yaml +++ b/helm/oncall/values.yaml @@ -239,6 +239,12 @@ externalRabbitmq: password: protocol: vhost: + # use an existing secret for the rabbitmq password + existingSecret: "" + # the key in the secret containing the rabbitmq password + passwordKey: password + # the key in the secret containing the rabbitmq username + usernameKey: username # Redis is included into this release for the convenience. # It is recommended to host it separately from this release From e103ec9152f0945deab760bf944c9d8a11975389 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Thu, 3 Nov 2022 11:21:42 -0300 Subject: [PATCH 45/50] Fix all-day end as datetime value Co-authored-by: Vadim Stepanov --- engine/apps/schedules/models/on_call_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/apps/schedules/models/on_call_schedule.py b/engine/apps/schedules/models/on_call_schedule.py index e2287ecd..bd79c190 100644 --- a/engine/apps/schedules/models/on_call_schedule.py +++ b/engine/apps/schedules/models/on_call_schedule.py @@ -227,7 +227,7 @@ class OnCallSchedule(PolymorphicModel): end = shift["end"] - timezone.timedelta(days=1) if all_day else shift["end"] if all_day and all_day_datetime: start = datetime.datetime.combine(start, datetime.datetime.min.time(), tzinfo=pytz.UTC) - end = datetime.datetime.combine(start, datetime.datetime.max.time(), tzinfo=pytz.UTC) + end = datetime.datetime.combine(end, datetime.datetime.max.time(), tzinfo=pytz.UTC) is_gap = shift.get("is_gap", False) shift_json = { "all_day": all_day, From e9df5ab5c9cd7620fec8843b87240a2a3fa5594c Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 3 Nov 2022 14:45:13 +0000 Subject: [PATCH 46/50] Send emails from .grafana.net for cloud (#766) --- engine/apps/email/tasks.py | 6 +- engine/apps/email/tests/test_notify_user.py | 69 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/engine/apps/email/tasks.py b/engine/apps/email/tasks.py index d9054745..403d4cdc 100644 --- a/engine/apps/email/tasks.py +++ b/engine/apps/email/tasks.py @@ -76,9 +76,13 @@ def notify_user_async(user_pk, alert_group_pk, notification_policy_pk): subject, html_message = build_subject_and_message(alert_group, emails_left) message = strip_tags(html_message) - email_from = settings.EMAIL_HOST_USER recipient_list = [user.email] + if settings.LICENSE == settings.CLOUD_LICENSE_NAME: + email_from = "oncall@{}.grafana.net".format(user.organization.stack_slug) + else: + email_from = live_settings.EMAIL_HOST_USER + connection = get_connection( host=live_settings.EMAIL_HOST, port=live_settings.EMAIL_PORT, diff --git a/engine/apps/email/tests/test_notify_user.py b/engine/apps/email/tests/test_notify_user.py index f5a10601..321d420a 100644 --- a/engine/apps/email/tests/test_notify_user.py +++ b/engine/apps/email/tests/test_notify_user.py @@ -155,3 +155,72 @@ def test_notify_user_no_emails_left( log_record = notification_policy.personal_log_records.last() assert log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED assert log_record.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_MAIL_LIMIT_EXCEEDED + + +@pytest.mark.django_db +def test_notify_user_from_email_oss( + settings, + make_organization, + make_user_for_organization, + make_token_for_organization, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_user_notification_policy, +): + settings.LICENSE = settings.OPEN_SOURCE_LICENSE_NAME + settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = "test" + settings.EMAIL_HOST_USER = "test@test.com" + + organization = make_organization(stack_slug="example") + user = make_user_for_organization(organization) + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload) + + notification_policy = make_user_notification_policy( + user, + UserNotificationPolicy.Step.NOTIFY, + notify_by=8, + important=False, + ) + + notify_user_async(user.pk, alert_group.pk, notification_policy.pk) + assert mail.outbox[0].from_email == "test@test.com" + + +@pytest.mark.django_db +def test_notify_user_from_email_cloud( + settings, + make_organization, + make_user_for_organization, + make_token_for_organization, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_user_notification_policy, +): + settings.LICENSE = settings.CLOUD_LICENSE_NAME + settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + settings.EMAIL_HOST = "test" + + organization = make_organization(stack_slug="slug") + user = make_user_for_organization(organization) + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + + make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload) + + notification_policy = make_user_notification_policy( + user, + UserNotificationPolicy.Step.NOTIFY, + notify_by=8, + important=False, + ) + + notify_user_async(user.pk, alert_group.pk, notification_policy.pk) + assert mail.outbox[0].from_email == "oncall@slug.grafana.net" From e2456315af4856771b571df18e507df4dc8ade93 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 3 Nov 2022 16:18:37 +0000 Subject: [PATCH 47/50] Allow no-auth SMTP connection for email notifications (#759) * DEFAULT_FROM_EMAIL -> EMAIL_FROM * add EMAIL_FROM live setting * EMAIL_FROM -> EMAIL_FROM_ADDRESS * merge dev * add test for get_from_email * allow live settings to be null on internal API * remove redundant tests --- docs/sources/open-source/_index.md | 3 +- engine/apps/api/serializers/live_setting.py | 2 +- engine/apps/base/models/live_setting.py | 2 + engine/apps/email/tasks.py | 18 +++-- engine/apps/email/tests/test_notify_user.py | 78 ++++++--------------- engine/settings/base.py | 2 +- helm/oncall/templates/_env.tpl | 2 +- 7 files changed, 41 insertions(+), 66 deletions(-) diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md index fdc3a1a8..8463fd82 100644 --- a/docs/sources/open-source/_index.md +++ b/docs/sources/open-source/_index.md @@ -195,6 +195,7 @@ Grafana OnCall is capable of sending emails using SMTP as a user notification st - `EMAIL_HOST_USER` - SMTP server user - `EMAIL_HOST_PASSWORD` - SMTP server password - `EMAIL_PORT` (default is `587`) - SMTP server port -- `EMAIL_USE_TLS` (default is `True`) - to enable/disable TLS +- `EMAIL_USE_TLS` (default is `True`) - To enable/disable TLS +- `EMAIL_FROM_ADDRESS` (optional) - Email address used to send emails. If not specified, `EMAIL_HOST_USER` will be used. After enabling the email integration, it will be possible to use the `Notify by email` notification step in user settings. diff --git a/engine/apps/api/serializers/live_setting.py b/engine/apps/api/serializers/live_setting.py index bf431341..15806b0e 100644 --- a/engine/apps/api/serializers/live_setting.py +++ b/engine/apps/api/serializers/live_setting.py @@ -5,7 +5,7 @@ from apps.base.models import LiveSetting class LiveSettingSerializer(serializers.ModelSerializer): id = serializers.CharField(read_only=True, source="public_primary_key") - value = serializers.JSONField(allow_null=False) + value = serializers.JSONField(allow_null=True) class Meta: model = LiveSetting diff --git a/engine/apps/base/models/live_setting.py b/engine/apps/base/models/live_setting.py index 4e2664d7..09845e6f 100644 --- a/engine/apps/base/models/live_setting.py +++ b/engine/apps/base/models/live_setting.py @@ -38,6 +38,7 @@ class LiveSetting(models.Model): "EMAIL_HOST_USER", "EMAIL_HOST_PASSWORD", "EMAIL_USE_TLS", + "EMAIL_FROM_ADDRESS", "TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_NUMBER", @@ -61,6 +62,7 @@ class LiveSetting(models.Model): "EMAIL_HOST_USER": "SMTP server user", "EMAIL_HOST_PASSWORD": "SMTP server password", "EMAIL_USE_TLS": "SMTP enable/disable TLS", + "EMAIL_FROM_ADDRESS": "Email address used to send emails. If not specified, EMAIL_HOST_USER will be used.", "SLACK_SIGNING_SECRET": ( "Check