feat: opsgenie integration (#3429)

This commit is contained in:
Srikanth Chekuri 2023-08-31 20:50:55 +05:30 committed by GitHub
parent e596dd77bd
commit 218eb5379e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 499 additions and 68 deletions

View File

@ -81,6 +81,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{ basemodel.Feature{
Name: basemodel.AlertChannelMsTeams, Name: basemodel.AlertChannelMsTeams,
Active: false, Active: false,
@ -161,6 +168,13 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{ basemodel.Feature{
Name: basemodel.AlertChannelMsTeams, Name: basemodel.AlertChannelMsTeams,
Active: true, Active: true,
@ -241,6 +255,13 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{ basemodel.Feature{
Name: basemodel.AlertChannelMsTeams, Name: basemodel.AlertChannelMsTeams,
Active: true, Active: true,

View File

@ -20,6 +20,9 @@
"field_slack_recipient": "Recipient", "field_slack_recipient": "Recipient",
"field_slack_title": "Title", "field_slack_title": "Title",
"field_slack_description": "Description", "field_slack_description": "Description",
"field_opsgenie_api_key": "API Key",
"field_opsgenie_description": "Description",
"placeholder_opsgenie_description": "Description",
"field_webhook_username": "User Name (optional)", "field_webhook_username": "User Name (optional)",
"field_webhook_password": "Password (optional)", "field_webhook_password": "Password (optional)",
"field_pager_routing_key": "Routing Key", "field_pager_routing_key": "Routing Key",
@ -31,8 +34,12 @@
"field_pager_class": "Class", "field_pager_class": "Class",
"field_pager_client": "Client", "field_pager_client": "Client",
"field_pager_client_url": "Client URL", "field_pager_client_url": "Client URL",
"field_opsgenie_message": "Message",
"field_opsgenie_priority": "Priority",
"placeholder_slack_description": "Description", "placeholder_slack_description": "Description",
"placeholder_pager_description": "Description", "placeholder_pager_description": "Description",
"placeholder_opsgenie_message": "Message",
"placeholder_opsgenie_priority": "Priority",
"help_pager_client": "Shows up as event source in Pagerduty", "help_pager_client": "Shows up as event source in Pagerduty",
"help_pager_client_url": "Shows up as event source link in Pagerduty", "help_pager_client_url": "Shows up as event source link in Pagerduty",
"help_pager_class": "The class/type of the event", "help_pager_class": "The class/type of the event",
@ -43,6 +50,9 @@
"help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.", "help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.",
"help_webhook_password": "Specify a password or bearer token", "help_webhook_password": "Specify a password or bearer token",
"help_pager_description": "Shows up as description in pagerduty", "help_pager_description": "Shows up as description in pagerduty",
"help_opsgenie_message": "Shows up as message in opsgenie",
"help_opsgenie_priority": "Priority of the incident",
"help_opsgenie_description": "Shows up as description in opsgenie",
"channel_creation_done": "Successfully created the channel", "channel_creation_done": "Successfully created the channel",
"channel_creation_failed": "An unexpected error occurred while creating this channel", "channel_creation_failed": "An unexpected error occurred while creating this channel",
"channel_edit_done": "Channels Edited Successfully", "channel_edit_done": "Channels Edited Successfully",

View File

@ -0,0 +1,37 @@
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/createOpsgenie';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/channels', {
name: props.name,
opsgenie_configs: [
{
api_key: props.api_key,
description: props.description,
priority: props.priority,
message: props.message,
details: {
...props.detailsArray,
},
},
],
});
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,38 @@
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/editOpsgenie';
const editOpsgenie = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/channels/${props.id}`, {
name: props.name,
opsgenie_configs: [
{
send_resolved: true,
api_key: props.api_key,
description: props.description,
priority: props.priority,
message: props.message,
details: {
...props.detailsArray,
},
},
],
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default editOpsgenie;

View File

@ -0,0 +1,37 @@
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/createOpsgenie';
const testOpsgenie = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/testChannel', {
name: props.name,
opsgenie_configs: [
{
api_key: props.api_key,
description: props.description,
priority: props.priority,
message: props.message,
details: {
...props.detailsArray,
},
},
],
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default testOpsgenie;

View File

@ -6,6 +6,7 @@ export enum FeatureKeys {
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK', ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK', ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY', ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS', ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
DurationSort = 'DurationSort', DurationSort = 'DurationSort',
TimestampSort = 'TimestampSort', TimestampSort = 'TimestampSort',

View File

@ -40,6 +40,30 @@ export interface PagerChannel extends Channel {
details?: string; details?: string;
detailsArray?: Record<string, string>; detailsArray?: Record<string, string>;
} }
// OpsgenieChannel configures alert manager to send
// events to opsgenie
export interface OpsgenieChannel extends Channel {
// ref: https://prometheus.io/docs/alerting/latest/configuration/#opsgenie_config
api_key: string;
message?: string;
// A description of the incident
description?: string;
// A backlink to the sender of the notification.
source?: string;
// A set of arbitrary key/value pairs that provide further detail
// about the alert.
details?: string;
detailsArray?: Record<string, string>;
// Priority level of alert. Possible values are P1, P2, P3, P4, and P5.
priority?: string;
}
export const ValidatePagerChannel = (p: PagerChannel): string => { export const ValidatePagerChannel = (p: PagerChannel): string => {
if (!p) { if (!p) {
return 'Received unexpected input for this channel, please contact your administrator '; return 'Received unexpected input for this channel, please contact your administrator ';
@ -63,16 +87,14 @@ export const ValidatePagerChannel = (p: PagerChannel): string => {
return ''; return '';
}; };
export type ChannelType = export enum ChannelType {
| 'slack' Slack = 'slack',
| 'email' Email = 'email',
| 'webhook' Webhook = 'webhook',
| 'pagerduty' Pagerduty = 'pagerduty',
| 'msteams'; Opsgenie = 'opsgenie',
export const SlackType: ChannelType = 'slack'; MsTeams = 'msteams',
export const WebhookType: ChannelType = 'webhook'; }
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 {

View File

@ -1,4 +1,4 @@
import { PagerChannel } from './config'; import { OpsgenieChannel, PagerChannel } from './config';
export const PagerInitialConfig: Partial<PagerChannel> = { export const PagerInitialConfig: Partial<PagerChannel> = {
description: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }} description: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
@ -22,3 +22,31 @@ export const PagerInitialConfig: Partial<PagerChannel> = {
num_resolved: '{{ .Alerts.Resolved | len }}', num_resolved: '{{ .Alerts.Resolved | len }}',
}), }),
}; };
export const OpsgenieInitialConfig: Partial<OpsgenieChannel> = {
message: '{{ .CommonLabels.alertname }}',
description: `{{ if gt (len .Alerts.Firing) 0 -}}
Alerts Firing:
{{ range .Alerts.Firing }}
- Message: {{ .Annotations.description }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }} Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }} Source: {{ .GeneratorURL }}
{{ end }}
{{- end }}
{{ if gt (len .Alerts.Resolved) 0 -}}
Alerts Resolved:
{{ range .Alerts.Resolved }}
- Message: {{ .Annotations.description }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }} Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }} Source: {{ .GeneratorURL }}
{{ end }}
{{- end }}`,
priority:
'{{ if eq (index .Alerts 0).Labels.severity "critical" }}P1{{ else if eq (index .Alerts 0).Labels.severity "warning" }}P2{{ else if eq (index .Alerts 0).Labels.severity "info" }}P3{{ else }}P4{{ end }}',
};

View File

@ -1,9 +1,11 @@
import { Form } from 'antd'; import { Form } from 'antd';
import createMsTeamsApi from 'api/channels/createMsTeams'; import createMsTeamsApi from 'api/channels/createMsTeams';
import createOpsgenie from 'api/channels/createOpsgenie';
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 testMsTeamsApi from 'api/channels/testMsTeams';
import testOpsGenie from 'api/channels/testOpsgenie';
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';
@ -17,19 +19,17 @@ import { useTranslation } from 'react-i18next';
import { import {
ChannelType, ChannelType,
MsTeamsChannel, MsTeamsChannel,
MsTeamsType, OpsgenieChannel,
PagerChannel, PagerChannel,
PagerType,
SlackChannel, SlackChannel,
SlackType,
ValidatePagerChannel, ValidatePagerChannel,
WebhookChannel, WebhookChannel,
WebhookType,
} from './config'; } from './config';
import { PagerInitialConfig } from './defaults'; import { OpsgenieInitialConfig, PagerInitialConfig } from './defaults';
import { isChannelType } from './utils';
function CreateAlertChannels({ function CreateAlertChannels({
preType = 'slack', preType = ChannelType.Slack,
}: CreateAlertChannelsProps): JSX.Element { }: CreateAlertChannelsProps): JSX.Element {
// init namespace for translations // init namespace for translations
const { t } = useTranslation('channels'); const { t } = useTranslation('channels');
@ -37,7 +37,13 @@ function CreateAlertChannels({
const [formInstance] = Form.useForm(); const [formInstance] = Form.useForm();
const [selectedConfig, setSelectedConfig] = useState< const [selectedConfig, setSelectedConfig] = useState<
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel> Partial<
SlackChannel &
WebhookChannel &
PagerChannel &
MsTeamsChannel &
OpsgenieChannel
>
>({ >({
text: `{{ range .Alerts -}} text: `{{ range .Alerts -}}
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
@ -71,7 +77,7 @@ function CreateAlertChannels({
const currentType = type; const currentType = type;
setType(value as ChannelType); setType(value as ChannelType);
if (value === PagerType && currentType !== value) { if (value === ChannelType.Pagerduty && currentType !== value) {
// reset config to pager defaults // reset config to pager defaults
setSelectedConfig({ setSelectedConfig({
name: selectedConfig?.name, name: selectedConfig?.name,
@ -79,6 +85,13 @@ function CreateAlertChannels({
...PagerInitialConfig, ...PagerInitialConfig,
}); });
} }
if (value === ChannelType.Opsgenie && currentType !== value) {
setSelectedConfig((selectedConfig) => ({
...selectedConfig,
...OpsgenieInitialConfig,
}));
}
}, },
[type, selectedConfig], [type, selectedConfig],
); );
@ -239,6 +252,45 @@ function CreateAlertChannels({
setSavingState(false); setSavingState(false);
}, [t, notifications, preparePagerRequest]); }, [t, notifications, preparePagerRequest]);
const prepareOpsgenieRequest = useCallback(
() => ({
api_key: selectedConfig?.api_key || '',
name: selectedConfig?.name || '',
send_resolved: true,
description: selectedConfig?.description || '',
message: selectedConfig?.message || '',
priority: selectedConfig?.priority || '',
}),
[selectedConfig],
);
const onOpsgenieHandler = useCallback(async () => {
setSavingState(true);
try {
const response = await createOpsgenie(prepareOpsgenieRequest());
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);
}, [prepareOpsgenieRequest, t, notifications]);
const prepareMsTeamsRequest = useCallback( const prepareMsTeamsRequest = useCallback(
() => ({ () => ({
webhook_url: selectedConfig?.webhook_url || '', webhook_url: selectedConfig?.webhook_url || '',
@ -280,12 +332,15 @@ function CreateAlertChannels({
const onSaveHandler = useCallback( const onSaveHandler = useCallback(
async (value: ChannelType) => { async (value: ChannelType) => {
const functionMapper = { const functionMapper = {
[SlackType]: onSlackHandler, [ChannelType.Slack]: onSlackHandler,
[WebhookType]: onWebhookHandler, [ChannelType.Webhook]: onWebhookHandler,
[PagerType]: onPagerHandler, [ChannelType.Pagerduty]: onPagerHandler,
[MsTeamsType]: onMsTeamsHandler, [ChannelType.Opsgenie]: onOpsgenieHandler,
[ChannelType.MsTeams]: onMsTeamsHandler,
}; };
const functionToCall = functionMapper[value];
if (isChannelType(value)) {
const functionToCall = functionMapper[value as keyof typeof functionMapper];
if (functionToCall) { if (functionToCall) {
functionToCall(); functionToCall();
@ -295,11 +350,13 @@ function CreateAlertChannels({
description: t('selected_channel_invalid'), description: t('selected_channel_invalid'),
}); });
} }
}
}, },
[ [
onSlackHandler, onSlackHandler,
onWebhookHandler, onWebhookHandler,
onPagerHandler, onPagerHandler,
onOpsgenieHandler,
onMsTeamsHandler, onMsTeamsHandler,
notifications, notifications,
t, t,
@ -313,22 +370,26 @@ function CreateAlertChannels({
let request; let request;
let response; let response;
switch (channelType) { switch (channelType) {
case WebhookType: case ChannelType.Webhook:
request = prepareWebhookRequest(); request = prepareWebhookRequest();
response = await testWebhookApi(request); response = await testWebhookApi(request);
break; break;
case SlackType: case ChannelType.Slack:
request = prepareSlackRequest(); request = prepareSlackRequest();
response = await testSlackApi(request); response = await testSlackApi(request);
break; break;
case PagerType: case ChannelType.Pagerduty:
request = preparePagerRequest(); request = preparePagerRequest();
if (request) response = await testPagerApi(request); if (request) response = await testPagerApi(request);
break; break;
case MsTeamsType: case ChannelType.MsTeams:
request = prepareMsTeamsRequest(); request = prepareMsTeamsRequest();
response = await testMsTeamsApi(request); response = await testMsTeamsApi(request);
break; break;
case ChannelType.Opsgenie:
request = prepareOpsgenieRequest();
response = await testOpsGenie(request);
break;
default: default:
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
@ -361,6 +422,7 @@ function CreateAlertChannels({
prepareWebhookRequest, prepareWebhookRequest,
t, t,
preparePagerRequest, preparePagerRequest,
prepareOpsgenieRequest,
prepareSlackRequest, prepareSlackRequest,
prepareMsTeamsRequest, prepareMsTeamsRequest,
notifications, notifications,
@ -390,6 +452,7 @@ function CreateAlertChannels({
type, type,
...selectedConfig, ...selectedConfig,
...PagerInitialConfig, ...PagerInitialConfig,
...OpsgenieInitialConfig,
}, },
}} }}
/> />

View File

@ -0,0 +1,4 @@
import { ChannelType } from './config';
export const isChannelType = (type: string): type is ChannelType =>
Object.values(ChannelType).includes(type as ChannelType);

View File

@ -1,9 +1,11 @@
import { Form } from 'antd'; import { Form } from 'antd';
import editMsTeamsApi from 'api/channels/editMsTeams'; import editMsTeamsApi from 'api/channels/editMsTeams';
import editOpsgenie from 'api/channels/editOpsgenie';
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 testMsTeamsApi from 'api/channels/testMsTeams';
import testOpsgenie from 'api/channels/testOpsgenie';
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';
@ -11,14 +13,11 @@ import ROUTES from 'constants/routes';
import { import {
ChannelType, ChannelType,
MsTeamsChannel, MsTeamsChannel,
MsTeamsType, OpsgenieChannel,
PagerChannel, PagerChannel,
PagerType,
SlackChannel, SlackChannel,
SlackType,
ValidatePagerChannel, ValidatePagerChannel,
WebhookChannel, WebhookChannel,
WebhookType,
} from 'container/CreateAlertChannels/config'; } from 'container/CreateAlertChannels/config';
import FormAlertChannels from 'container/FormAlertChannels'; import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@ -35,7 +34,13 @@ function EditAlertChannels({
const [formInstance] = Form.useForm(); const [formInstance] = Form.useForm();
const [selectedConfig, setSelectedConfig] = useState< const [selectedConfig, setSelectedConfig] = useState<
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel> Partial<
SlackChannel &
WebhookChannel &
PagerChannel &
MsTeamsChannel &
OpsgenieChannel
>
>({ >({
...initialValue, ...initialValue,
}); });
@ -45,7 +50,7 @@ function EditAlertChannels({
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [type, setType] = useState<ChannelType>( const [type, setType] = useState<ChannelType>(
initialValue?.type ? (initialValue.type as ChannelType) : SlackType, initialValue?.type ? (initialValue.type as ChannelType) : ChannelType.Slack,
); );
const onTypeChangeHandler = useCallback((value: string) => { const onTypeChangeHandler = useCallback((value: string) => {
@ -193,6 +198,48 @@ function EditAlertChannels({
setSavingState(false); setSavingState(false);
}, [preparePagerRequest, notifications, selectedConfig, t]); }, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback(
() => ({
name: selectedConfig.name || '',
api_key: selectedConfig.api_key || '',
message: selectedConfig.message || '',
description: selectedConfig.description || '',
priority: selectedConfig.priority || '',
id,
}),
[id, selectedConfig],
);
const onOpsgenieEditHandler = useCallback(async () => {
setSavingState(true);
if (selectedConfig?.api_key === '') {
notifications.error({
message: 'Error',
description: t('api_key_required'),
});
setSavingState(false);
return;
}
const response = await editOpsgenie(prepareOpsgenieRequest());
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);
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback( const prepareMsTeamsRequest = useCallback(
() => ({ () => ({
webhook_url: selectedConfig?.webhook_url || '', webhook_url: selectedConfig?.webhook_url || '',
@ -237,14 +284,16 @@ function EditAlertChannels({
const onSaveHandler = useCallback( const onSaveHandler = useCallback(
(value: ChannelType) => { (value: ChannelType) => {
if (value === SlackType) { if (value === ChannelType.Slack) {
onSlackEditHandler(); onSlackEditHandler();
} else if (value === WebhookType) { } else if (value === ChannelType.Webhook) {
onWebhookEditHandler(); onWebhookEditHandler();
} else if (value === PagerType) { } else if (value === ChannelType.Pagerduty) {
onPagerEditHandler(); onPagerEditHandler();
} else if (value === MsTeamsType) { } else if (value === ChannelType.MsTeams) {
onMsTeamsEditHandler(); onMsTeamsEditHandler();
} else if (value === ChannelType.Opsgenie) {
onOpsgenieEditHandler();
} }
}, },
[ [
@ -252,6 +301,7 @@ function EditAlertChannels({
onWebhookEditHandler, onWebhookEditHandler,
onPagerEditHandler, onPagerEditHandler,
onMsTeamsEditHandler, onMsTeamsEditHandler,
onOpsgenieEditHandler,
], ],
); );
@ -262,22 +312,26 @@ function EditAlertChannels({
let request; let request;
let response; let response;
switch (channelType) { switch (channelType) {
case WebhookType: case ChannelType.Webhook:
request = prepareWebhookRequest(); request = prepareWebhookRequest();
response = await testWebhookApi(request); response = await testWebhookApi(request);
break; break;
case SlackType: case ChannelType.Slack:
request = prepareSlackRequest(); request = prepareSlackRequest();
response = await testSlackApi(request); response = await testSlackApi(request);
break; break;
case PagerType: case ChannelType.Pagerduty:
request = preparePagerRequest(); request = preparePagerRequest();
if (request) response = await testPagerApi(request); if (request) response = await testPagerApi(request);
break; break;
case MsTeamsType: case ChannelType.MsTeams:
request = prepareMsTeamsRequest(); request = prepareMsTeamsRequest();
if (request) response = await testMsTeamsApi(request); if (request) response = await testMsTeamsApi(request);
break; break;
case ChannelType.Opsgenie:
request = prepareOpsgenieRequest();
if (request) response = await testOpsgenie(request);
break;
default: default:
notifications.error({ notifications.error({
message: 'Error', message: 'Error',
@ -312,6 +366,7 @@ function EditAlertChannels({
preparePagerRequest, preparePagerRequest,
prepareSlackRequest, prepareSlackRequest,
prepareMsTeamsRequest, prepareMsTeamsRequest,
prepareOpsgenieRequest,
notifications, notifications,
], ],
); );

View File

@ -0,0 +1,74 @@
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import { OpsgenieChannel } from '../../CreateAlertChannels/config';
const { TextArea } = Input;
function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
const { t } = useTranslation('channels');
const handleInputChange = (field: string) => (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
): void => {
setSelectedConfig((value) => ({
...value,
[field]: event.target.value,
}));
};
return (
<>
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
<Input onChange={handleInputChange('api_key')} />
</Form.Item>
<Form.Item
name="message"
help={t('help_opsgenie_message')}
label={t('field_opsgenie_message')}
required
>
<TextArea
rows={4}
onChange={handleInputChange('message')}
placeholder={t('placeholder_opsgenie_message')}
/>
</Form.Item>
<Form.Item
name="description"
help={t('help_opsgenie_description')}
label={t('field_opsgenie_description')}
required
>
<TextArea
rows={4}
onChange={handleInputChange('description')}
placeholder={t('placeholder_opsgenie_description')}
/>
</Form.Item>
<Form.Item
name="priority"
help={t('help_opsgenie_priority')}
label={t('field_opsgenie_priority')}
required
>
<TextArea
rows={4}
onChange={handleInputChange('priority')}
placeholder={t('placeholder_opsgenie_priority')}
/>
</Form.Item>
</>
);
}
interface OpsgenieFormProps {
setSelectedConfig: React.Dispatch<
React.SetStateAction<Partial<OpsgenieChannel>>
>;
}
export default OpsgenieForm;

View File

@ -5,13 +5,10 @@ import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { import {
ChannelType, ChannelType,
MsTeamsType, OpsgenieChannel,
PagerChannel, PagerChannel,
PagerType,
SlackChannel, SlackChannel,
SlackType,
WebhookChannel, WebhookChannel,
WebhookType,
} from 'container/CreateAlertChannels/config'; } from 'container/CreateAlertChannels/config';
import useFeatureFlags from 'hooks/useFeatureFlag'; import useFeatureFlags from 'hooks/useFeatureFlag';
import { isFeatureKeys } from 'hooks/useFeatureFlag/utils'; import { isFeatureKeys } from 'hooks/useFeatureFlag/utils';
@ -20,6 +17,7 @@ import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MsTeamsSettings from './Settings/MsTeams'; import MsTeamsSettings from './Settings/MsTeams';
import OpsgenieSettings from './Settings/Opsgenie';
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';
@ -61,14 +59,16 @@ function FormAlertChannels({
} }
switch (type) { switch (type) {
case SlackType: case ChannelType.Slack:
return <SlackSettings setSelectedConfig={setSelectedConfig} />; return <SlackSettings setSelectedConfig={setSelectedConfig} />;
case WebhookType: case ChannelType.Webhook:
return <WebhookSettings setSelectedConfig={setSelectedConfig} />; return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
case PagerType: case ChannelType.Pagerduty:
return <PagerSettings setSelectedConfig={setSelectedConfig} />; return <PagerSettings setSelectedConfig={setSelectedConfig} />;
case MsTeamsType: case ChannelType.MsTeams:
return <MsTeamsSettings setSelectedConfig={setSelectedConfig} />; return <MsTeamsSettings setSelectedConfig={setSelectedConfig} />;
case ChannelType.Opsgenie:
return <OpsgenieSettings setSelectedConfig={setSelectedConfig} />;
default: default:
return null; return null;
} }
@ -102,6 +102,9 @@ function FormAlertChannels({
<Select.Option value="pagerduty" key="pagerduty"> <Select.Option value="pagerduty" key="pagerduty">
Pagerduty Pagerduty
</Select.Option> </Select.Option>
<Select.Option value="opsgenie" key="opsgenie">
Opsgenie
</Select.Option>
{!isOssFeature?.active && ( {!isOssFeature?.active && (
<Select.Option value="msteams" key="msteams"> <Select.Option value="msteams" key="msteams">
<div> <div>
@ -147,7 +150,9 @@ interface FormAlertChannelsProps {
formInstance: FormInstance; formInstance: FormInstance;
type: ChannelType; type: ChannelType;
setSelectedConfig: Dispatch< setSelectedConfig: Dispatch<
SetStateAction<Partial<SlackChannel & WebhookChannel & PagerChannel>> SetStateAction<
Partial<SlackChannel & WebhookChannel & PagerChannel & OpsgenieChannel>
>
>; >;
onTypeChangeHandler: (value: ChannelType) => void; onTypeChangeHandler: (value: ChannelType) => void;
onSaveHandler: (props: ChannelType) => void; onSaveHandler: (props: ChannelType) => void;

View File

@ -1,5 +1,6 @@
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import CreateAlertChannels from 'container/CreateAlertChannels'; import CreateAlertChannels from 'container/CreateAlertChannels';
import { ChannelType } from 'container/CreateAlertChannels/config';
import GeneralSettings from 'container/GeneralSettings'; import GeneralSettings from 'container/GeneralSettings';
import { t } from 'i18next'; import { t } from 'i18next';
@ -11,7 +12,9 @@ export const alertsRoutesConfig = [
key: ROUTES.SETTINGS, key: ROUTES.SETTINGS,
}, },
{ {
Component: (): JSX.Element => <CreateAlertChannels preType="slack" />, Component: (): JSX.Element => (
<CreateAlertChannels preType={ChannelType.Slack} />
),
name: t('routes.alert_channels'), name: t('routes.alert_channels'),
route: ROUTES.CHANNELS_NEW, route: ROUTES.CHANNELS_NEW,
key: ROUTES.CHANNELS_NEW, key: ROUTES.CHANNELS_NEW,

View File

@ -1,15 +1,13 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { Typography } from 'antd'; 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 {
ChannelType,
MsTeamsChannel, MsTeamsChannel,
MsTeamsType,
PagerChannel, PagerChannel,
PagerType,
SlackChannel, SlackChannel,
SlackType,
WebhookChannel, WebhookChannel,
WebhookType,
} from 'container/CreateAlertChannels/config'; } from 'container/CreateAlertChannels/config';
import EditAlertChannels from 'container/EditAlertChannels'; import EditAlertChannels from 'container/EditAlertChannels';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -50,7 +48,7 @@ function ChannelsEdit(): JSX.Element {
const slackConfig = value.slack_configs[0]; const slackConfig = value.slack_configs[0];
channel = slackConfig; channel = slackConfig;
return { return {
type: SlackType, type: ChannelType.Slack,
channel, channel,
}; };
} }
@ -59,7 +57,7 @@ function ChannelsEdit(): JSX.Element {
const msteamsConfig = value.msteams_configs[0]; const msteamsConfig = value.msteams_configs[0];
channel = msteamsConfig; channel = msteamsConfig;
return { return {
type: MsTeamsType, type: ChannelType.MsTeams,
channel, channel,
}; };
} }
@ -69,7 +67,16 @@ function ChannelsEdit(): JSX.Element {
channel.details = JSON.stringify(pagerConfig.details); channel.details = JSON.stringify(pagerConfig.details);
channel.detailsArray = { ...pagerConfig.details }; channel.detailsArray = { ...pagerConfig.details };
return { return {
type: PagerType, type: ChannelType.Pagerduty,
channel,
};
}
if (value && 'opsgenie_configs' in value) {
const opsgenieConfig = value.opsgenie_configs[0];
channel = opsgenieConfig;
return {
type: ChannelType.Opsgenie,
channel, channel,
}; };
} }
@ -89,12 +96,12 @@ function ChannelsEdit(): JSX.Element {
} }
} }
return { return {
type: WebhookType, type: ChannelType.Webhook,
channel, channel,
}; };
} }
return { return {
type: SlackType, type: ChannelType.Slack,
channel, channel,
}; };
}; };

View File

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

View File

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

View File

@ -20,6 +20,7 @@ const AlertChannelSlack = "ALERT_CHANNEL_SLACK"
const AlertChannelWebhook = "ALERT_CHANNEL_WEBHOOK" const AlertChannelWebhook = "ALERT_CHANNEL_WEBHOOK"
const AlertChannelPagerduty = "ALERT_CHANNEL_PAGERDUTY" const AlertChannelPagerduty = "ALERT_CHANNEL_PAGERDUTY"
const AlertChannelMsTeams = "ALERT_CHANNEL_MSTEAMS" const AlertChannelMsTeams = "ALERT_CHANNEL_MSTEAMS"
const AlertChannelOpsgenie = "ALERT_CHANNEL_OPSGENIE"
var BasicPlan = FeatureSet{ var BasicPlan = FeatureSet{
Feature{ Feature{
@ -92,6 +93,13 @@ var BasicPlan = FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
Feature{
Name: AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
Feature{ Feature{
Name: AlertChannelMsTeams, Name: AlertChannelMsTeams,
Active: false, Active: false,