feat: add ms teams channels (#2689)

* feat: api machinery to support enterprise plan channels

* feat: backend for handling ms teams

* feat: frontend for ms teams

* fix: fixed some minor issues wiht ms teams

* fix: resolved issue with feature gate

* chore: add missing span metrics

* chore: some minor changes are updated

* feat: added the oss flag is updated

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Amol Umbark 2023-08-15 21:19:05 +05:30 committed by GitHub
parent c37d6c3785
commit 2bf534b56f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 631 additions and 60 deletions

View File

@ -60,6 +60,34 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: 5,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
@ -112,6 +140,34 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
@ -164,6 +220,34 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,

View File

@ -0,0 +1,34 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/channels', {
name: props.name,
msteams_configs: [
{
send_resolved: true,
webhook_url: props.webhook_url,
title: props.title,
text: props.text,
},
],
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default create;

View File

@ -0,0 +1,34 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/editMsTeams';
const editMsTeams = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
name: props.name,
msteams_configs: [
{
send_resolved: true,
webhook_url: props.webhook_url,
title: props.title,
text: props.text,
},
],
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default editMsTeams;

View File

@ -0,0 +1,34 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
const testMsTeams = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/testChannel', {
name: props.name,
msteams_configs: [
{
send_resolved: true,
webhook_url: props.webhook_url,
title: props.title,
text: props.text,
},
],
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default testMsTeams;

View File

@ -0,0 +1,31 @@
import { Alert, Space } from 'antd';
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
type UpgradePromptProps = {
title?: string;
};
function UpgradePrompt({ title }: UpgradePromptProps): JSX.Element {
return (
<Space direction="vertical" style={{ width: '100%' }}>
<Alert
message={title}
description={
<div>
This feature is available for paid plans only.{' '}
<a href={SIGNOZ_UPGRADE_PLAN_URL} target="_blank" rel="noreferrer">
Click here
</a>{' '}
to Upgrade
</div>
}
type="warning"
/>{' '}
</Space>
);
}
UpgradePrompt.defaultProps = {
title: 'Upgrade to a Paid Plan',
};
export default UpgradePrompt;

View File

@ -1,6 +1,12 @@
// keep this consistent with backend constants.go
export enum FeatureKeys {
SSO = 'SSO',
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
BASIC_PLAN = 'BASIC_PLAN',
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
DurationSort = 'DurationSort',
TimestampSort = 'TimestampSort',
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
@ -9,4 +15,5 @@ export enum FeatureKeys {
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
DISABLE_UPSELL = 'DISABLE_UPSELL',
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
OSS = 'OSS',
}

View File

@ -63,10 +63,16 @@ export const ValidatePagerChannel = (p: PagerChannel): string => {
return '';
};
export type ChannelType = 'slack' | 'email' | 'webhook' | 'pagerduty';
export type ChannelType =
| 'slack'
| 'email'
| 'webhook'
| 'pagerduty'
| 'msteams';
export const SlackType: ChannelType = 'slack';
export const WebhookType: ChannelType = 'webhook';
export const PagerType: ChannelType = 'pagerduty';
export const MsTeamsType: ChannelType = 'msteams';
// LabelFilterStatement will be used for preparing filter conditions / matchers
export interface LabelFilterStatement {
@ -81,3 +87,9 @@ export interface LabelFilterStatement {
// filter value
value: string;
}
export interface MsTeamsChannel extends Channel {
webhook_url?: string;
title?: string;
text?: string;
}

View File

@ -1,7 +1,9 @@
import { Form } from 'antd';
import createMsTeamsApi from 'api/channels/createMsTeams';
import createPagerApi from 'api/channels/createPager';
import createSlackApi from 'api/channels/createSlack';
import createWebhookApi from 'api/channels/createWebhook';
import testMsTeamsApi from 'api/channels/testMsTeams';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
@ -14,6 +16,8 @@ import { useTranslation } from 'react-i18next';
import {
ChannelType,
MsTeamsChannel,
MsTeamsType,
PagerChannel,
PagerType,
SlackChannel,
@ -33,7 +37,7 @@ function CreateAlertChannels({
const [formInstance] = Form.useForm();
const [selectedConfig, setSelectedConfig] = useState<
Partial<SlackChannel & WebhookChannel & PagerChannel>
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
>({
text: `{{ range .Alerts -}}
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
@ -102,9 +106,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
@ -165,9 +167,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
@ -222,9 +222,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
@ -241,26 +239,71 @@ function CreateAlertChannels({
setSavingState(false);
}, [t, notifications, preparePagerRequest]);
const prepareMsTeamsRequest = useCallback(
() => ({
webhook_url: selectedConfig?.webhook_url || '',
name: selectedConfig?.name || '',
send_resolved: true,
text: selectedConfig?.text || '',
title: selectedConfig?.title || '',
}),
[selectedConfig],
);
const onMsTeamsHandler = useCallback(async () => {
setSavingState(true);
try {
const response = await createMsTeamsApi(prepareMsTeamsRequest());
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
}
setSavingState(false);
}, [prepareMsTeamsRequest, t, notifications]);
const onSaveHandler = useCallback(
async (value: ChannelType) => {
switch (value) {
case SlackType:
onSlackHandler();
break;
case WebhookType:
onWebhookHandler();
break;
case PagerType:
onPagerHandler();
break;
default:
notifications.error({
message: 'Error',
description: t('selected_channel_invalid'),
});
const functionMapper = {
[SlackType]: onSlackHandler,
[WebhookType]: onWebhookHandler,
[PagerType]: onPagerHandler,
[MsTeamsType]: onMsTeamsHandler,
};
const functionToCall = functionMapper[value];
if (functionToCall) {
functionToCall();
} else {
notifications.error({
message: 'Error',
description: t('selected_channel_invalid'),
});
}
},
[onSlackHandler, t, onPagerHandler, onWebhookHandler, notifications],
[
onSlackHandler,
onWebhookHandler,
onPagerHandler,
onMsTeamsHandler,
notifications,
t,
],
);
const performChannelTest = useCallback(
@ -282,6 +325,10 @@ function CreateAlertChannels({
request = preparePagerRequest();
if (request) response = await testPagerApi(request);
break;
case MsTeamsType:
request = prepareMsTeamsRequest();
response = await testMsTeamsApi(request);
break;
default:
notifications.error({
message: 'Error',
@ -315,6 +362,7 @@ function CreateAlertChannels({
t,
preparePagerRequest,
prepareSlackRequest,
prepareMsTeamsRequest,
notifications,
],
);

View File

@ -1,13 +1,17 @@
import { Form } from 'antd';
import editMsTeamsApi from 'api/channels/editMsTeams';
import editPagerApi from 'api/channels/editPager';
import editSlackApi from 'api/channels/editSlack';
import editWebhookApi from 'api/channels/editWebhook';
import testMsTeamsApi from 'api/channels/testMsTeams';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
import ROUTES from 'constants/routes';
import {
ChannelType,
MsTeamsChannel,
MsTeamsType,
PagerChannel,
PagerType,
SlackChannel,
@ -31,7 +35,7 @@ function EditAlertChannels({
const [formInstance] = Form.useForm();
const [selectedConfig, setSelectedConfig] = useState<
Partial<SlackChannel & WebhookChannel & PagerChannel>
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
>({
...initialValue,
});
@ -81,9 +85,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
@ -136,9 +138,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
showError(response.error || t('channel_edit_failed'));
}
@ -183,9 +183,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
}, 2000);
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
@ -195,6 +193,48 @@ function EditAlertChannels({
setSavingState(false);
}, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareMsTeamsRequest = useCallback(
() => ({
webhook_url: selectedConfig?.webhook_url || '',
name: selectedConfig?.name || '',
send_resolved: true,
text: selectedConfig?.text || '',
title: selectedConfig?.title || '',
id,
}),
[id, selectedConfig],
);
const onMsTeamsEditHandler = useCallback(async () => {
setSavingState(true);
if (selectedConfig?.webhook_url === '') {
notifications.error({
message: 'Error',
description: t('webhook_url_required'),
});
setSavingState(false);
return;
}
const response = await editMsTeamsApi(prepareMsTeamsRequest());
if (response.statusCode === 200) {
notifications.success({
message: 'Success',
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
setSavingState(false);
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback(
(value: ChannelType) => {
if (value === SlackType) {
@ -203,9 +243,16 @@ function EditAlertChannels({
onWebhookEditHandler();
} else if (value === PagerType) {
onPagerEditHandler();
} else if (value === MsTeamsType) {
onMsTeamsEditHandler();
}
},
[onSlackEditHandler, onWebhookEditHandler, onPagerEditHandler],
[
onSlackEditHandler,
onWebhookEditHandler,
onPagerEditHandler,
onMsTeamsEditHandler,
],
);
const performChannelTest = useCallback(
@ -227,6 +274,10 @@ function EditAlertChannels({
request = preparePagerRequest();
if (request) response = await testPagerApi(request);
break;
case MsTeamsType:
request = prepareMsTeamsRequest();
if (request) response = await testMsTeamsApi(request);
break;
default:
notifications.error({
message: 'Error',
@ -260,6 +311,7 @@ function EditAlertChannels({
prepareWebhookRequest,
preparePagerRequest,
prepareSlackRequest,
prepareMsTeamsRequest,
notifications,
],
);

View File

@ -0,0 +1,57 @@
import { Form, Input } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { MsTeamsChannel } from '../../CreateAlertChannels/config';
function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
const { t } = useTranslation('channels');
return (
<>
<Form.Item name="webhook_url" label={t('field_webhook_url')}>
<Input
onChange={(event): void => {
setSelectedConfig((value) => ({
...value,
webhook_url: event.target.value,
}));
}}
/>
</Form.Item>
<Form.Item name="title" label={t('field_slack_title')}>
<Input.TextArea
rows={4}
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
onChange={(event): void =>
setSelectedConfig((value) => ({
...value,
title: event.target.value,
}))
}
/>
</Form.Item>
<Form.Item name="text" label={t('field_slack_description')}>
<Input.TextArea
onChange={(event): void =>
setSelectedConfig((value) => ({
...value,
text: event.target.value,
}))
}
placeholder={t('placeholder_slack_description')}
/>
</Form.Item>
</>
);
}
interface MsTeamsProps {
setSelectedConfig: React.Dispatch<
React.SetStateAction<Partial<MsTeamsChannel>>
>;
}
export default MsTeams;

View File

@ -1,8 +1,11 @@
import { Form, FormInstance, Input, Select, Typography } from 'antd';
import { Store } from 'antd/lib/form/interface';
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import {
ChannelType,
MsTeamsType,
PagerChannel,
PagerType,
SlackChannel,
@ -10,18 +13,18 @@ import {
WebhookChannel,
WebhookType,
} from 'container/CreateAlertChannels/config';
import useFeatureFlags from 'hooks/useFeatureFlag';
import { isFeatureKeys } from 'hooks/useFeatureFlag/utils';
import history from 'lib/history';
import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import MsTeamsSettings from './Settings/MsTeams';
import PagerSettings from './Settings/Pager';
import SlackSettings from './Settings/Slack';
import WebhookSettings from './Settings/Webhook';
import { Button } from './styles';
const { Option } = Select;
const { Title } = Typography;
function FormAlertChannels({
formInstance,
type,
@ -36,8 +39,27 @@ function FormAlertChannels({
editing = false,
}: FormAlertChannelsProps): JSX.Element {
const { t } = useTranslation('channels');
const isUserOnEEPlan = useFeatureFlags(FeatureKeys.ENTERPRISE_PLAN);
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
const hasFeature = useFeatureFlags(
isFeatureKeys(feature) ? feature : FeatureKeys.ALERT_CHANNEL_SLACK,
);
const isOssFeature = useFeatureFlags(FeatureKeys.OSS);
const renderSettings = (): ReactElement | null => {
if (
// for ee plan
!isOssFeature?.active &&
(!hasFeature || !hasFeature.active) &&
type === 'msteams'
) {
// channel type is not available for users plan
return <UpgradePrompt />;
}
switch (type) {
case SlackType:
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
@ -45,14 +67,16 @@ function FormAlertChannels({
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
case PagerType:
return <PagerSettings setSelectedConfig={setSelectedConfig} />;
case MsTeamsType:
return <MsTeamsSettings setSelectedConfig={setSelectedConfig} />;
default:
return null;
}
};
return (
<>
<Title level={3}>{title}</Title>
<Typography.Title level={3}>{title}</Typography.Title>
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
@ -69,15 +93,22 @@ function FormAlertChannels({
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
<Option value="slack" key="slack">
<Select.Option value="slack" key="slack">
Slack
</Option>
<Option value="webhook" key="webhook">
</Select.Option>
<Select.Option value="webhook" key="webhook">
Webhook
</Option>
<Option value="pagerduty" key="pagerduty">
</Select.Option>
<Select.Option value="pagerduty" key="pagerduty">
Pagerduty
</Option>
</Select.Option>
{!isOssFeature?.active && (
<Select.Option value="msteams" key="msteams">
<div>
Microsoft Teams {!isUserOnEEPlan && '(Supported in Paid Plans Only)'}{' '}
</div>
</Select.Option>
)}
</Select>
</Form.Item>
@ -85,7 +116,7 @@ function FormAlertChannels({
<Form.Item>
<Button
disabled={savingState}
disabled={savingState || !hasFeature}
loading={savingState}
type="primary"
onClick={(): void => onSaveHandler(type)}
@ -93,7 +124,7 @@ function FormAlertChannels({
{t('button_save_channel')}
</Button>
<Button
disabled={testingState}
disabled={testingState || !hasFeature}
loading={testingState}
onClick={(): void => onTestHandler(type)}
>

View File

@ -0,0 +1,13 @@
import { FeatureKeys } from 'constants/features';
import { isFeatureKeys } from './utils';
describe('Feature Keys', () => {
it('should return true for a valid feature key', () => {
expect(isFeatureKeys(FeatureKeys.ALERT_CHANNEL_MSTEAMS)).toBe(true);
});
it('should return false for an invalid feature key', () => {
expect(isFeatureKeys('invalid')).toBe(false);
});
});

View File

@ -0,0 +1,4 @@
import { FeatureKeys } from 'constants/features';
export const isFeatureKeys = (key: string): key is keyof typeof FeatureKeys =>
Object.keys(FeatureKeys).includes(key);

View File

@ -2,6 +2,8 @@ import { Typography } from 'antd';
import get from 'api/channels/get';
import Spinner from 'components/Spinner';
import {
MsTeamsChannel,
MsTeamsType,
PagerChannel,
PagerType,
SlackChannel,
@ -39,9 +41,11 @@ function ChannelsEdit(): JSX.Element {
const prepChannelConfig = (): {
type: string;
channel: SlackChannel & WebhookChannel & PagerChannel;
channel: SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel;
} => {
let channel: SlackChannel & WebhookChannel & PagerChannel = { name: '' };
let channel: SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel = {
name: '',
};
if (value && 'slack_configs' in value) {
const slackConfig = value.slack_configs[0];
channel = slackConfig;
@ -50,6 +54,15 @@ function ChannelsEdit(): JSX.Element {
channel,
};
}
if (value && 'msteams_configs' in value) {
const msteamsConfig = value.msteams_configs[0];
channel = msteamsConfig;
return {
type: MsTeamsType,
channel,
};
}
if (value && 'pagerduty_configs' in value) {
const pagerConfig = value.pagerduty_configs[0];
channel = pagerConfig;

View File

@ -0,0 +1,8 @@
import { MsTeamsChannel } from 'container/CreateAlertChannels/config';
export type Props = MsTeamsChannel;
export interface PayloadProps {
data: string;
status: string;
}

View File

@ -0,0 +1,10 @@
import { MsTeamsChannel } from 'container/CreateAlertChannels/config';
export interface Props extends MsTeamsChannel {
id: string;
}
export interface PayloadProps {
data: string;
status: string;
}

View File

@ -559,7 +559,9 @@ func getChannelType(receiver *am.Receiver) string {
if receiver.WechatConfigs != nil {
return "wechat"
}
if receiver.MSTeamsConfigs != nil {
return "msteams"
}
return ""
}
@ -582,6 +584,13 @@ func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Re
}
channel_type := getChannelType(receiver)
// check if channel type is supported in the current user plan
if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
zap.S().Warn("an unsupported feature was blocked", err)
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
}
receiverString, _ := json.Marshal(receiver)
{
@ -619,16 +628,21 @@ func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Re
func (r *ClickHouseReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
channel_type := getChannelType(receiver)
// check if channel type is supported in the current user plan
if err := r.featureFlags.CheckFeature(fmt.Sprintf("ALERT_CHANNEL_%s", strings.ToUpper(channel_type))); err != nil {
zap.S().Warn("an unsupported feature was blocked", err)
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unsupported feature. please upgrade your plan to access this feature")}
}
receiverString, _ := json.Marshal(receiver)
tx, err := r.localDB.Begin()
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
channel_type := getChannelType(receiver)
receiverString, _ := json.Marshal(receiver)
// todo: check if the channel name already exists, raise an error if so
{
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
if err != nil {

View File

@ -1128,7 +1128,6 @@ func (aH *APIHandler) testChannel(w http.ResponseWriter, r *http.Request) {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
return
}
// send alert
apiErrorObj := aH.alertManager.TestReceiver(receiver)
if apiErrorObj != nil {

View File

@ -21,6 +21,7 @@ type Receiver struct {
PushoverConfigs interface{} `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs interface{} `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
SNSConfigs interface{} `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
MSTeamsConfigs interface{} `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"`
}
type ReceiverResponse struct {

View File

@ -11,7 +11,92 @@ type Feature struct {
const SmartTraceDetail = "SMART_TRACE_DETAIL"
const CustomMetricsFunction = "CUSTOM_METRICS_FUNCTION"
const DisableUpsell = "DISABLE_UPSELL"
const OSS = "OSS"
const QueryBuilderPanels = "QUERY_BUILDER_PANELS"
const QueryBuilderAlerts = "QUERY_BUILDER_ALERTS"
const UseSpanMetrics = "USE_SPAN_METRICS"
const AlertChannelSlack = "ALERT_CHANNEL_SLACK"
const AlertChannelWebhook = "ALERT_CHANNEL_WEBHOOK"
const AlertChannelPagerduty = "ALERT_CHANNEL_PAGERDUTY"
const AlertChannelMsTeams = "ALERT_CHANNEL_MSTEAMS"
var BasicPlan = FeatureSet{
Feature{
Name: OSS,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: DisableUpsell,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: SmartTraceDetail,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: CustomMetricsFunction,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: 5,
Route: "",
},
Feature{
Name: QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: 5,
Route: "",
},
Feature{
Name: UseSpanMetrics,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{
Name: AlertChannelMsTeams,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}