mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-04 11:25:52 +08:00
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:
parent
c37d6c3785
commit
2bf534b56f
@ -60,6 +60,34 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: 5,
|
UsageLimit: 5,
|
||||||
Route: "",
|
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{
|
basemodel.Feature{
|
||||||
Name: basemodel.UseSpanMetrics,
|
Name: basemodel.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
@ -112,6 +140,34 @@ var ProPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
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{
|
basemodel.Feature{
|
||||||
Name: basemodel.UseSpanMetrics,
|
Name: basemodel.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
@ -164,6 +220,34 @@ var EnterprisePlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
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{
|
basemodel.Feature{
|
||||||
Name: basemodel.UseSpanMetrics,
|
Name: basemodel.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
|
34
frontend/src/api/channels/createMsTeams.ts
Normal file
34
frontend/src/api/channels/createMsTeams.ts
Normal 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;
|
34
frontend/src/api/channels/editMsTeams.ts
Normal file
34
frontend/src/api/channels/editMsTeams.ts
Normal 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;
|
34
frontend/src/api/channels/testMsTeams.ts
Normal file
34
frontend/src/api/channels/testMsTeams.ts
Normal 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;
|
31
frontend/src/components/Upgrade/UpgradePrompt.tsx
Normal file
31
frontend/src/components/Upgrade/UpgradePrompt.tsx
Normal 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;
|
@ -1,6 +1,12 @@
|
|||||||
// keep this consistent with backend constants.go
|
// keep this consistent with backend constants.go
|
||||||
export enum FeatureKeys {
|
export enum FeatureKeys {
|
||||||
SSO = 'SSO',
|
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',
|
DurationSort = 'DurationSort',
|
||||||
TimestampSort = 'TimestampSort',
|
TimestampSort = 'TimestampSort',
|
||||||
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
|
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
|
||||||
@ -9,4 +15,5 @@ export enum FeatureKeys {
|
|||||||
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
||||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||||
|
OSS = 'OSS',
|
||||||
}
|
}
|
||||||
|
@ -63,10 +63,16 @@ export const ValidatePagerChannel = (p: PagerChannel): string => {
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChannelType = 'slack' | 'email' | 'webhook' | 'pagerduty';
|
export type ChannelType =
|
||||||
|
| 'slack'
|
||||||
|
| 'email'
|
||||||
|
| 'webhook'
|
||||||
|
| 'pagerduty'
|
||||||
|
| 'msteams';
|
||||||
export const SlackType: ChannelType = 'slack';
|
export const SlackType: ChannelType = 'slack';
|
||||||
export const WebhookType: ChannelType = 'webhook';
|
export const WebhookType: ChannelType = 'webhook';
|
||||||
export const PagerType: ChannelType = 'pagerduty';
|
export const PagerType: ChannelType = 'pagerduty';
|
||||||
|
export const MsTeamsType: ChannelType = 'msteams';
|
||||||
|
|
||||||
// LabelFilterStatement will be used for preparing filter conditions / matchers
|
// LabelFilterStatement will be used for preparing filter conditions / matchers
|
||||||
export interface LabelFilterStatement {
|
export interface LabelFilterStatement {
|
||||||
@ -81,3 +87,9 @@ export interface LabelFilterStatement {
|
|||||||
// filter value
|
// filter value
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MsTeamsChannel extends Channel {
|
||||||
|
webhook_url?: string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
|
import createMsTeamsApi from 'api/channels/createMsTeams';
|
||||||
import createPagerApi from 'api/channels/createPager';
|
import createPagerApi from 'api/channels/createPager';
|
||||||
import createSlackApi from 'api/channels/createSlack';
|
import createSlackApi from 'api/channels/createSlack';
|
||||||
import createWebhookApi from 'api/channels/createWebhook';
|
import createWebhookApi from 'api/channels/createWebhook';
|
||||||
|
import testMsTeamsApi from 'api/channels/testMsTeams';
|
||||||
import testPagerApi from 'api/channels/testPager';
|
import testPagerApi from 'api/channels/testPager';
|
||||||
import testSlackApi from 'api/channels/testSlack';
|
import testSlackApi from 'api/channels/testSlack';
|
||||||
import testWebhookApi from 'api/channels/testWebhook';
|
import testWebhookApi from 'api/channels/testWebhook';
|
||||||
@ -14,6 +16,8 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
|
MsTeamsChannel,
|
||||||
|
MsTeamsType,
|
||||||
PagerChannel,
|
PagerChannel,
|
||||||
PagerType,
|
PagerType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
@ -33,7 +37,7 @@ function CreateAlertChannels({
|
|||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
|
|
||||||
const [selectedConfig, setSelectedConfig] = useState<
|
const [selectedConfig, setSelectedConfig] = useState<
|
||||||
Partial<SlackChannel & WebhookChannel & PagerChannel>
|
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
|
||||||
>({
|
>({
|
||||||
text: `{{ range .Alerts -}}
|
text: `{{ range .Alerts -}}
|
||||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||||
@ -102,9 +106,7 @@ function CreateAlertChannels({
|
|||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -165,9 +167,7 @@ function CreateAlertChannels({
|
|||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -222,9 +222,7 @@ function CreateAlertChannels({
|
|||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('channel_creation_done'),
|
description: t('channel_creation_done'),
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -241,26 +239,71 @@ function CreateAlertChannels({
|
|||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
}, [t, notifications, preparePagerRequest]);
|
}, [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(
|
const onSaveHandler = useCallback(
|
||||||
async (value: ChannelType) => {
|
async (value: ChannelType) => {
|
||||||
switch (value) {
|
const functionMapper = {
|
||||||
case SlackType:
|
[SlackType]: onSlackHandler,
|
||||||
onSlackHandler();
|
[WebhookType]: onWebhookHandler,
|
||||||
break;
|
[PagerType]: onPagerHandler,
|
||||||
case WebhookType:
|
[MsTeamsType]: onMsTeamsHandler,
|
||||||
onWebhookHandler();
|
};
|
||||||
break;
|
const functionToCall = functionMapper[value];
|
||||||
case PagerType:
|
|
||||||
onPagerHandler();
|
if (functionToCall) {
|
||||||
break;
|
functionToCall();
|
||||||
default:
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('selected_channel_invalid'),
|
description: t('selected_channel_invalid'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onSlackHandler, t, onPagerHandler, onWebhookHandler, notifications],
|
[
|
||||||
|
onSlackHandler,
|
||||||
|
onWebhookHandler,
|
||||||
|
onPagerHandler,
|
||||||
|
onMsTeamsHandler,
|
||||||
|
notifications,
|
||||||
|
t,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const performChannelTest = useCallback(
|
const performChannelTest = useCallback(
|
||||||
@ -282,6 +325,10 @@ function CreateAlertChannels({
|
|||||||
request = preparePagerRequest();
|
request = preparePagerRequest();
|
||||||
if (request) response = await testPagerApi(request);
|
if (request) response = await testPagerApi(request);
|
||||||
break;
|
break;
|
||||||
|
case MsTeamsType:
|
||||||
|
request = prepareMsTeamsRequest();
|
||||||
|
response = await testMsTeamsApi(request);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -315,6 +362,7 @@ function CreateAlertChannels({
|
|||||||
t,
|
t,
|
||||||
preparePagerRequest,
|
preparePagerRequest,
|
||||||
prepareSlackRequest,
|
prepareSlackRequest,
|
||||||
|
prepareMsTeamsRequest,
|
||||||
notifications,
|
notifications,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
|
import editMsTeamsApi from 'api/channels/editMsTeams';
|
||||||
import editPagerApi from 'api/channels/editPager';
|
import editPagerApi from 'api/channels/editPager';
|
||||||
import editSlackApi from 'api/channels/editSlack';
|
import editSlackApi from 'api/channels/editSlack';
|
||||||
import editWebhookApi from 'api/channels/editWebhook';
|
import editWebhookApi from 'api/channels/editWebhook';
|
||||||
|
import testMsTeamsApi from 'api/channels/testMsTeams';
|
||||||
import testPagerApi from 'api/channels/testPager';
|
import testPagerApi from 'api/channels/testPager';
|
||||||
import testSlackApi from 'api/channels/testSlack';
|
import testSlackApi from 'api/channels/testSlack';
|
||||||
import testWebhookApi from 'api/channels/testWebhook';
|
import testWebhookApi from 'api/channels/testWebhook';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
|
MsTeamsChannel,
|
||||||
|
MsTeamsType,
|
||||||
PagerChannel,
|
PagerChannel,
|
||||||
PagerType,
|
PagerType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
@ -31,7 +35,7 @@ function EditAlertChannels({
|
|||||||
|
|
||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
const [selectedConfig, setSelectedConfig] = useState<
|
const [selectedConfig, setSelectedConfig] = useState<
|
||||||
Partial<SlackChannel & WebhookChannel & PagerChannel>
|
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
|
||||||
>({
|
>({
|
||||||
...initialValue,
|
...initialValue,
|
||||||
});
|
});
|
||||||
@ -81,9 +85,7 @@ function EditAlertChannels({
|
|||||||
description: t('channel_edit_done'),
|
description: t('channel_edit_done'),
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -136,9 +138,7 @@ function EditAlertChannels({
|
|||||||
description: t('channel_edit_done'),
|
description: t('channel_edit_done'),
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
showError(response.error || t('channel_edit_failed'));
|
showError(response.error || t('channel_edit_failed'));
|
||||||
}
|
}
|
||||||
@ -183,9 +183,7 @@ function EditAlertChannels({
|
|||||||
description: t('channel_edit_done'),
|
description: t('channel_edit_done'),
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
history.replace(ROUTES.ALL_CHANNELS);
|
||||||
history.replace(ROUTES.SETTINGS);
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -195,6 +193,48 @@ function EditAlertChannels({
|
|||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
}, [preparePagerRequest, notifications, selectedConfig, t]);
|
}, [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(
|
const onSaveHandler = useCallback(
|
||||||
(value: ChannelType) => {
|
(value: ChannelType) => {
|
||||||
if (value === SlackType) {
|
if (value === SlackType) {
|
||||||
@ -203,9 +243,16 @@ function EditAlertChannels({
|
|||||||
onWebhookEditHandler();
|
onWebhookEditHandler();
|
||||||
} else if (value === PagerType) {
|
} else if (value === PagerType) {
|
||||||
onPagerEditHandler();
|
onPagerEditHandler();
|
||||||
|
} else if (value === MsTeamsType) {
|
||||||
|
onMsTeamsEditHandler();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onSlackEditHandler, onWebhookEditHandler, onPagerEditHandler],
|
[
|
||||||
|
onSlackEditHandler,
|
||||||
|
onWebhookEditHandler,
|
||||||
|
onPagerEditHandler,
|
||||||
|
onMsTeamsEditHandler,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const performChannelTest = useCallback(
|
const performChannelTest = useCallback(
|
||||||
@ -227,6 +274,10 @@ function EditAlertChannels({
|
|||||||
request = preparePagerRequest();
|
request = preparePagerRequest();
|
||||||
if (request) response = await testPagerApi(request);
|
if (request) response = await testPagerApi(request);
|
||||||
break;
|
break;
|
||||||
|
case MsTeamsType:
|
||||||
|
request = prepareMsTeamsRequest();
|
||||||
|
if (request) response = await testMsTeamsApi(request);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
@ -260,6 +311,7 @@ function EditAlertChannels({
|
|||||||
prepareWebhookRequest,
|
prepareWebhookRequest,
|
||||||
preparePagerRequest,
|
preparePagerRequest,
|
||||||
prepareSlackRequest,
|
prepareSlackRequest,
|
||||||
|
prepareMsTeamsRequest,
|
||||||
notifications,
|
notifications,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -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;
|
@ -1,8 +1,11 @@
|
|||||||
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from 'antd/lib/form/interface';
|
||||||
|
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
|
MsTeamsType,
|
||||||
PagerChannel,
|
PagerChannel,
|
||||||
PagerType,
|
PagerType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
@ -10,18 +13,18 @@ import {
|
|||||||
WebhookChannel,
|
WebhookChannel,
|
||||||
WebhookType,
|
WebhookType,
|
||||||
} from 'container/CreateAlertChannels/config';
|
} from 'container/CreateAlertChannels/config';
|
||||||
|
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||||
|
import { isFeatureKeys } from 'hooks/useFeatureFlag/utils';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import MsTeamsSettings from './Settings/MsTeams';
|
||||||
import PagerSettings from './Settings/Pager';
|
import PagerSettings from './Settings/Pager';
|
||||||
import SlackSettings from './Settings/Slack';
|
import SlackSettings from './Settings/Slack';
|
||||||
import WebhookSettings from './Settings/Webhook';
|
import WebhookSettings from './Settings/Webhook';
|
||||||
import { Button } from './styles';
|
import { Button } from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
const { Title } = Typography;
|
|
||||||
|
|
||||||
function FormAlertChannels({
|
function FormAlertChannels({
|
||||||
formInstance,
|
formInstance,
|
||||||
type,
|
type,
|
||||||
@ -36,8 +39,27 @@ function FormAlertChannels({
|
|||||||
editing = false,
|
editing = false,
|
||||||
}: FormAlertChannelsProps): JSX.Element {
|
}: FormAlertChannelsProps): JSX.Element {
|
||||||
const { t } = useTranslation('channels');
|
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 => {
|
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) {
|
switch (type) {
|
||||||
case SlackType:
|
case SlackType:
|
||||||
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
|
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
@ -45,14 +67,16 @@ function FormAlertChannels({
|
|||||||
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
|
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
case PagerType:
|
case PagerType:
|
||||||
return <PagerSettings setSelectedConfig={setSelectedConfig} />;
|
return <PagerSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
|
case MsTeamsType:
|
||||||
|
return <MsTeamsSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title level={3}>{title}</Title>
|
<Typography.Title level={3}>{title}</Typography.Title>
|
||||||
|
|
||||||
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
||||||
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
|
<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">
|
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
||||||
<Option value="slack" key="slack">
|
<Select.Option value="slack" key="slack">
|
||||||
Slack
|
Slack
|
||||||
</Option>
|
</Select.Option>
|
||||||
<Option value="webhook" key="webhook">
|
<Select.Option value="webhook" key="webhook">
|
||||||
Webhook
|
Webhook
|
||||||
</Option>
|
</Select.Option>
|
||||||
<Option value="pagerduty" key="pagerduty">
|
<Select.Option value="pagerduty" key="pagerduty">
|
||||||
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>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -85,7 +116,7 @@ function FormAlertChannels({
|
|||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
disabled={savingState}
|
disabled={savingState || !hasFeature}
|
||||||
loading={savingState}
|
loading={savingState}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={(): void => onSaveHandler(type)}
|
onClick={(): void => onSaveHandler(type)}
|
||||||
@ -93,7 +124,7 @@ function FormAlertChannels({
|
|||||||
{t('button_save_channel')}
|
{t('button_save_channel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={testingState}
|
disabled={testingState || !hasFeature}
|
||||||
loading={testingState}
|
loading={testingState}
|
||||||
onClick={(): void => onTestHandler(type)}
|
onClick={(): void => onTestHandler(type)}
|
||||||
>
|
>
|
||||||
|
13
frontend/src/hooks/useFeatureFlag/utils.test.ts
Normal file
13
frontend/src/hooks/useFeatureFlag/utils.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
4
frontend/src/hooks/useFeatureFlag/utils.ts
Normal file
4
frontend/src/hooks/useFeatureFlag/utils.ts
Normal 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);
|
@ -2,6 +2,8 @@ import { Typography } from 'antd';
|
|||||||
import get from 'api/channels/get';
|
import get from 'api/channels/get';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import {
|
import {
|
||||||
|
MsTeamsChannel,
|
||||||
|
MsTeamsType,
|
||||||
PagerChannel,
|
PagerChannel,
|
||||||
PagerType,
|
PagerType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
@ -39,9 +41,11 @@ function ChannelsEdit(): JSX.Element {
|
|||||||
|
|
||||||
const prepChannelConfig = (): {
|
const prepChannelConfig = (): {
|
||||||
type: string;
|
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) {
|
if (value && 'slack_configs' in value) {
|
||||||
const slackConfig = value.slack_configs[0];
|
const slackConfig = value.slack_configs[0];
|
||||||
channel = slackConfig;
|
channel = slackConfig;
|
||||||
@ -50,6 +54,15 @@ function ChannelsEdit(): JSX.Element {
|
|||||||
channel,
|
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) {
|
if (value && 'pagerduty_configs' in value) {
|
||||||
const pagerConfig = value.pagerduty_configs[0];
|
const pagerConfig = value.pagerduty_configs[0];
|
||||||
channel = pagerConfig;
|
channel = pagerConfig;
|
||||||
|
8
frontend/src/types/api/channels/createMsTeams.ts
Normal file
8
frontend/src/types/api/channels/createMsTeams.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { MsTeamsChannel } from 'container/CreateAlertChannels/config';
|
||||||
|
|
||||||
|
export type Props = MsTeamsChannel;
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: string;
|
||||||
|
status: string;
|
||||||
|
}
|
10
frontend/src/types/api/channels/editMsTeams.ts
Normal file
10
frontend/src/types/api/channels/editMsTeams.ts
Normal 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;
|
||||||
|
}
|
@ -559,7 +559,9 @@ func getChannelType(receiver *am.Receiver) string {
|
|||||||
if receiver.WechatConfigs != nil {
|
if receiver.WechatConfigs != nil {
|
||||||
return "wechat"
|
return "wechat"
|
||||||
}
|
}
|
||||||
|
if receiver.MSTeamsConfigs != nil {
|
||||||
|
return "msteams"
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,6 +584,13 @@ func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel_type := getChannelType(receiver)
|
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)
|
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) {
|
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()
|
tx, err := r.localDB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
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);`)
|
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1128,7 +1128,6 @@ func (aH *APIHandler) testChannel(w http.ResponseWriter, r *http.Request) {
|
|||||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// send alert
|
// send alert
|
||||||
apiErrorObj := aH.alertManager.TestReceiver(receiver)
|
apiErrorObj := aH.alertManager.TestReceiver(receiver)
|
||||||
if apiErrorObj != nil {
|
if apiErrorObj != nil {
|
||||||
|
@ -21,6 +21,7 @@ type Receiver struct {
|
|||||||
PushoverConfigs interface{} `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
PushoverConfigs interface{} `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
||||||
VictorOpsConfigs interface{} `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
VictorOpsConfigs interface{} `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
||||||
SNSConfigs interface{} `yaml:"sns_configs,omitempty" json:"sns_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 {
|
type ReceiverResponse struct {
|
||||||
|
@ -11,7 +11,92 @@ type Feature struct {
|
|||||||
|
|
||||||
const SmartTraceDetail = "SMART_TRACE_DETAIL"
|
const SmartTraceDetail = "SMART_TRACE_DETAIL"
|
||||||
const CustomMetricsFunction = "CUSTOM_METRICS_FUNCTION"
|
const CustomMetricsFunction = "CUSTOM_METRICS_FUNCTION"
|
||||||
|
const DisableUpsell = "DISABLE_UPSELL"
|
||||||
const OSS = "OSS"
|
const OSS = "OSS"
|
||||||
const QueryBuilderPanels = "QUERY_BUILDER_PANELS"
|
const QueryBuilderPanels = "QUERY_BUILDER_PANELS"
|
||||||
const QueryBuilderAlerts = "QUERY_BUILDER_ALERTS"
|
const QueryBuilderAlerts = "QUERY_BUILDER_ALERTS"
|
||||||
const UseSpanMetrics = "USE_SPAN_METRICS"
|
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: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user