Skip to content

Commit

Permalink
Merge pull request #4898 from grafana/dev
Browse files Browse the repository at this point in the history
1.9.10
  • Loading branch information
Konstantinov-Innokentii authored Aug 22, 2024
2 parents 9a81c8b + b5c5225 commit ca5a5f5
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 121 deletions.
339 changes: 339 additions & 0 deletions docs/sources/configure/integrations/references/slack/index.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/sources/manage/notify/slack/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,22 @@ refs:
destination: /docs/oncall/<ONCALL_VERSION>/set-up/open-source/#slack-setup
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/oncall/set-up/open-source/#slack-setup
irm-slack:
- pattern: /docs/oncall/
destination: /docs/oncall/<ONCALL_VERSION>/configure/integrations/references/slack/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/oncall/configure/integrations/references/slack/
---

# Slack integration for Grafana OnCall

{{< admonition type="warning" >}}
The OnCall Slack app is now a Grafana IRM app with new incident management features. Migrate now to access the enhanced capabilities.

Refer to the [Grafana IRM Slack integration](ref:irm-slack) documentation to learn more.
{{< /admonition >}}

{{< collapse title="Legacy Slack integration for Grafana OnCall" >}}
The Slack integration for Grafana OnCall incorporates your Slack workspace directly into your incident response workflow
to help your team focus on alert resolution with less friction.

Expand Down Expand Up @@ -210,3 +222,4 @@ Use message shortcuts to add resolution notes directly from Slack. Message short
1. Hover over the message and select **More actions** from the menu options.
1. Select **Add as resolution note**.
1. The Grafana OnCall app will react to the message in Slack with the memo emoji and add the message to the alert group timeline.
{{< /collapse >}}
6 changes: 3 additions & 3 deletions docs/sources/set-up/get-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ refs:
destination: /docs/grafana-cloud/alerting-and-irm/oncall/configure/escalation-chains-and-routes/#escalation-chains
slack-integration-for-grafana-oncall:
- pattern: /docs/oncall/
destination: /docs/oncall/<ONCALL_VERSION>/manage/notify/slack/
destination: /docs/oncall/<ONCALL_VERSION>/configure/integrations/references/slack/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/oncall/manage/notify/slack/
destination: /docs/grafana-cloud/alerting-and-irm/oncall/configure/integrations/references/slack/
user-and-team-management:
- pattern: /docs/oncall/
destination: /docs/oncall/<ONCALL_VERSION>/manage/user-and-team-management/
Expand Down Expand Up @@ -233,7 +233,7 @@ To configure Slack for Grafana OnCall:
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](ref:slack-integration-for-grafana-oncall)
[Slack integration for Grafana IRM](ref:slack-integration-for-grafana-oncall).

Grafana OnCall also supports other ChatOps integration like Microsoft Teams and Telegram.
For a full list of supported integrations, refer to [Notify people](ref:notify-people).
Expand Down
10 changes: 7 additions & 3 deletions engine/apps/slack/scenarios/paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,16 @@ def process_scenario(
if settings.UNIFIED_SLACK_APP_ENABLED:
if slack_team_identity.needs_reinstall:
organizations = _get_available_organizations(slack_team_identity, slack_user_identity)
# Provide a link to web if user has access only to one organization
if len(organizations) == 1:
# Provide a link to web if user has access only to one organization
link = urljoin(organizations[0].web_link, "settings?tab=ChatOps&chatOpsTab=Slack")
upgrade = f"<{link}|Upgrade>"
else:
upgrade = "Upgrade" # TODO: Add link to docs are available
# Otherwise, provide a link to the documentation
link = (
"https://grafana.com/docs/grafana-cloud/alerting-and-irm/oncall/configure/integrations"
"/references/slack/#migrate-to-the-grafana-irm-slack-integration"
)
upgrade = f"<{link}|Upgrade>"
msg = (
f"The new Slack IRM integration is now available. f{upgrade} for a more powerful and flexible "
f"way to interact with Grafana IRM on Slack."
Expand Down
2 changes: 1 addition & 1 deletion engine/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
METRIC_USER_WAS_NOTIFIED_OF_ALERT_GROUPS_NAME,
]
# List of metrics to collect. Collect all available application metrics by default
METRICS_TO_COLLECT = os.environ.get("METRICS_TO_COLLECT", METRICS_ALL)
METRICS_TO_COLLECT = getenv_list("METRICS_TO_COLLECT", METRICS_ALL)


# Database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const ApiTokenForm = observer((props: TokenCreationModalProps) => {
<VerticalGroup>
<Label>Curl command example</Label>
<SourceCode noMinHeight showClipboardIconOnly>
{getCurlExample(token, store.onCallApiUrl)}
{getCurlExample(token, store.pluginStore.apiUrlFromStatus)}
</SourceCode>
</VerticalGroup>
);
Expand Down
6 changes: 4 additions & 2 deletions grafana-plugin/src/models/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { OnCallPluginMetaJSONData } from 'types';
import { ActionKey } from 'models/loader/action-keys';
import { GrafanaApiClient } from 'network/grafana-api/http-client';
import { makeRequest } from 'network/network';
import { PluginConnection, PostStatusResponse } from 'network/oncall-api/api.types';
import { PluginConnection, StatusResponse } from 'network/oncall-api/api.types';
import { RootBaseStore } from 'state/rootBaseStore/RootBaseStore';
import { waitInMs } from 'utils/async';
import { AutoLoadingState } from 'utils/decorators';
Expand All @@ -31,6 +31,7 @@ On Cloud:
export class PluginStore {
rootStore: RootBaseStore;
connectionStatus?: PluginConnection;
apiUrlFromStatus?: string;
isPluginConnected = false;
appliedOnCallApiUrl = '';

Expand All @@ -53,9 +54,10 @@ export class PluginStore {

@AutoLoadingState(ActionKey.PLUGIN_VERIFY_CONNECTION)
async verifyPluginConnection() {
const { pluginConnection } = await makeRequest<PostStatusResponse>(`/plugin/status`, {});
const { pluginConnection, api_url } = await makeRequest<StatusResponse>(`/plugin/status`, {});
runInAction(() => {
this.connectionStatus = pluginConnection;
this.apiUrlFromStatus = api_url;
this.isPluginConnected = Object.keys(pluginConnection).every(
(key) => pluginConnection[key as keyof PluginConnection]?.ok
);
Expand Down
7 changes: 1 addition & 6 deletions grafana-plugin/src/network/oncall-api/api.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@ type PluginConnection = {
grafana_url_from_engine: PluginConnectionCheck;
};

export type PostStatusResponse = {
export type StatusResponse = {
pluginConnection: PluginConnection;
allow_signup: boolean;
api_url: string;
currently_undergoing_maintenance_message: string | null;
is_installed: boolean;
is_user_anonymous: boolean;
license: string;
recaptcha_site_key: string;
token_ok: boolean;
version: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { WithStoreProps } from 'state/types';
import { useStore } from 'state/useStore';
import { withMobXProviderContext } from 'state/withStore';
import { UserActions } from 'utils/authorization/authorization';
import { DOCS_ROOT, DOCS_SLACK_SETUP, getPluginId } from 'utils/consts';
import { DOCS_SLACK_SETUP, getPluginId } from 'utils/consts';
import { useConfirmModal } from 'utils/hooks';
import { showApiError } from 'utils/utils';

Expand Down Expand Up @@ -353,15 +353,16 @@ const UpgradeToUnifiedSlackBanner = observer(() => {
} = useStore();
const { modalProps, openModal } = useConfirmModal();

const SLACK_MIGRATION_DOCS =
'https://grafana.com/docs/grafana-cloud/alerting-and-irm/oncall/configure/integrations/references/slack/#migrate-to-the-grafana-irm-slack-integration';
return (
<>
<ConfirmModal {...modalProps} />
<Alert severity="warning" title="This integration is outdated" buttonContent="Migrate">
<div className={styles.upgradeSlackAlertText}>
The OnCall Slack app is now a Grafana IRM app with new incident management features. Migrate now to access the
enhanced capabilities.{' '}
<a href={`${DOCS_ROOT}`} target="_blank" rel="noreferrer">
{/* TODO: update link to docs */}
<a href={`${SLACK_MIGRATION_DOCS}`} target="_blank" rel="noreferrer">
Learn more
</a>
</div>
Expand Down Expand Up @@ -390,12 +391,7 @@ const UpgradeToUnifiedSlackBanner = observer(() => {
</li>
</ul>
</p>
<a
href={`${DOCS_ROOT}`} // TODO: update link to docs
target="_blank"
rel="noreferrer"
className={styles.marginTop}
>
<a href={`${SLACK_MIGRATION_DOCS}`} target="_blank" rel="noreferrer" className={styles.marginTop}>
<Text type="link">
<span>Learn more in the docs</span>
<Icon name="external-link-alt" className="u-margin-left-xs u-margin-bottom-xxs" />
Expand Down

This file was deleted.

139 changes: 62 additions & 77 deletions grafana-plugin/src/pages/settings/tabs/MainSettings/MainSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { Field, Input, Switch } from '@grafana/ui';
import cn from 'classnames/bind';
import { css } from '@emotion/css';
import { Field, Input, Switch, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import { LegacyNavHeading } from 'navbar/LegacyNavHeading';

Expand All @@ -10,86 +10,71 @@ import { ApiTokenSettings } from 'containers/ApiTokenSettings/ApiTokenSettings';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { TeamsSettings } from 'pages/settings/tabs/TeamsSettings/TeamsSettings';
import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { useStore } from 'state/useStore';
import { UserActions } from 'utils/authorization/authorization';

import styles from './MainSettings.module.css';
export const MainSettings = observer(() => {
const styles = useStyles2(getStyles);
const {
organizationStore: { currentOrganization, saveCurrentOrganization },
pluginStore: { apiUrlFromStatus },
} = useStore();

const cx = cn.bind(styles);

interface SettingsPageProps extends WithStoreProps {}

interface SettingsPageState {
apiUrl?: string;
}

@observer
class Settings extends React.Component<SettingsPageProps, SettingsPageState> {
state: SettingsPageState = {
apiUrl: '',
};

async componentDidMount() {
const { store } = this.props;
const url = await store.getApiUrlForSettings();
this.setState({ apiUrl: url });
}

render() {
const { organizationStore } = this.props.store;
const { currentOrganization } = organizationStore;
const { apiUrl } = this.state;

return (
<div className={cx('root')}>
<LegacyNavHeading>
<Text.Title level={3} className={cx('title')}>
Organization settings
</Text.Title>
</LegacyNavHeading>
return (
<div>
<LegacyNavHeading>
<Text.Title level={3} className={styles.title}>
Organization settings
</Text.Title>
</LegacyNavHeading>

<div className={cx('settings')}>
<Text.Title level={3} className={cx('title')}>
Resolution Note
</Text.Title>
<Field
loading={!currentOrganization}
label="Require a resolution note when resolving Alert Groups"
description={`Once user clicks "Resolve" for an Alert Group, they will be required to fill in a resolution note about the Alert Group`}
>
<WithPermissionControlTooltip userAction={UserActions.OtherSettingsWrite}>
<Switch
value={currentOrganization?.is_resolution_note_required}
onChange={(event) => {
organizationStore.saveCurrentOrganization({
is_resolution_note_required: event.currentTarget.checked,
});
}}
/>
</WithPermissionControlTooltip>
</Field>
</div>
{!isTopNavbar() && (
<div style={{ marginBottom: '20px' }}>
<Text.Title level={3} className={cx('title')}>
Teams and Access Settings
</Text.Title>
<TeamsSettings />
</div>
)}
<Text.Title level={3} className={cx('title')}>
API URL
<div className={styles.settings}>
<Text.Title level={3} className={styles.title}>
Resolution Note
</Text.Title>
<div>
<Field>
<Input value={apiUrl} disabled />
</Field>
<Field
loading={!currentOrganization}
label="Require a resolution note when resolving Alert Groups"
description={`Once user clicks "Resolve" for an Alert Group, they will be required to fill in a resolution note about the Alert Group`}
>
<WithPermissionControlTooltip userAction={UserActions.OtherSettingsWrite}>
<Switch
value={currentOrganization?.is_resolution_note_required}
onChange={(event) => {
saveCurrentOrganization({
is_resolution_note_required: event.currentTarget.checked,
});
}}
/>
</WithPermissionControlTooltip>
</Field>
</div>
{!isTopNavbar() && (
<div style={{ marginBottom: '20px' }}>
<Text.Title level={3} className={styles.title}>
Teams and Access Settings
</Text.Title>
<TeamsSettings />
</div>
<ApiTokenSettings />
)}
<Text.Title level={3} className={styles.title}>
API URL
</Text.Title>
<div>
<Field>
<Input value={apiUrlFromStatus} disabled />
</Field>
</div>
);
}
}
<ApiTokenSettings />
</div>
);
});

export const MainSettings = withMobXProviderContext(Settings);
const getStyles = () => ({
settings: css`
width: fit-content;
`,
title: css`
margin-bottom: 20px;
`,
});
8 changes: 0 additions & 8 deletions grafana-plugin/src/state/rootBaseStore/RootBaseStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ export class RootBaseStore {
@observable
pageTitle = '';

@observable
onCallApiUrl: string;

@observable
insightsDatasource = 'grafanacloud-usage';

Expand Down Expand Up @@ -186,11 +183,6 @@ export class RootBaseStore {
this.pageTitle = title;
}

@action.bound
async getApiUrlForSettings() {
return this.onCallApiUrl;
}

@action.bound
async loadRecaptcha() {
const { recaptcha_site_key } = await makeRequest<{ recaptcha_site_key: string }>('/plugin/recaptcha');
Expand Down
4 changes: 1 addition & 3 deletions grafana-plugin/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AppRootProps as BaseAppRootProps, AppPluginMeta, PluginConfigPageProps, BootData } from '@grafana/data';
import { AppRootProps as BaseAppRootProps, AppPluginMeta, PluginConfigPageProps } from '@grafana/data';

import { getPluginId } from 'utils/consts';

Expand Down Expand Up @@ -30,8 +30,6 @@ export type OnCallPluginExtensionPoints =

declare global {
export interface Window {
// https://github.com/grafana/grafana/blob/78bef7a26a799209b5307d6bde8e25fcb4fbde7d/public/views/index-template.html#L251-L258
grafanaBootData?: BootData;
RECAPTCHA_SITE_KEY: string;
grecaptcha: any;
dataLayer: any;
Expand Down
2 changes: 1 addition & 1 deletion tools/migrators/lib/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def api_call(method: str, base_url: str, path: str, **kwargs) -> requests.Respon
if e.response.status_code == 429:
cooldown_seconds = int(e.response.headers["Retry-After"])
sleep(cooldown_seconds)
return api_call(method, path, **kwargs)
return api_call(method, base_url, path, **kwargs)
elif e.response.status_code == 400:
resp_json = None
with suppress(requests.exceptions.JSONDecodeError):
Expand Down

0 comments on commit ca5a5f5

Please sign in to comment.