diff --git a/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.module.css b/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.module.css
index 6c4f6366..43b4e87e 100644
--- a/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.module.css
+++ b/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.module.css
@@ -49,3 +49,11 @@
.payloadExample {
margin-top: 24px;
}
+
+.autoresolve-condition section {
+ border: 1px solid var(--primary-text-link);
+}
+
+.autoresolve-label {
+ margin-bottom: 0 !important;
+}
diff --git a/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.tsx b/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.tsx
index 18191c14..ac749fa5 100644
--- a/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.tsx
+++ b/grafana-plugin/src/components/AlertTemplates/AlertTemplatesForm.tsx
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { SelectableValue } from '@grafana/data';
+import { getLocationSrv } from '@grafana/runtime';
import { Label, Button, HorizontalGroup, VerticalGroup, Select, LoadingPlaceholder } from '@grafana/ui';
import { capitalCase } from 'change-case';
import cn from 'classnames/bind';
@@ -33,6 +34,7 @@ interface AlertTemplatesFormProps {
demoAlertEnabled: boolean;
handleSendDemoAlertClick: () => void;
templatesRefreshing: boolean;
+ selectedTemplateName?: string;
}
const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
@@ -45,6 +47,7 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
demoAlertEnabled,
handleSendDemoAlertClick,
templatesRefreshing,
+ selectedTemplateName,
} = props;
const [tempValues, setTempValues] = useState<{
@@ -117,15 +120,29 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
[groups, activeGroup]
);
+ const getGroupByTemplateName = (templateName: string) => {
+ Object.values(groups).find((group) => {
+ const foundTemplate = group.find((obj: any) => {
+ if (obj.name == templateName) {
+ return obj;
+ }
+ });
+ setActiveGroup(foundTemplate?.group);
+ });
+ };
+
const handleChangeActiveGroup = useCallback((group: SelectableValue) => {
setActiveGroup(group.value);
}, []);
useEffect(() => {
const groupsArr = Object.keys(groups);
-
- if (!activeGroup && groupsArr.length) {
- setActiveGroup(groupsArr[0]);
+ if (selectedTemplateName) {
+ getGroupByTemplateName(selectedTemplateName);
+ } else {
+ if (!activeGroup && groupsArr.length) {
+ setActiveGroup(groupsArr[0]);
+ }
}
}, [groups, activeGroup]);
@@ -134,6 +151,7 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
setActiveTemplate(groups[activeGroup][0]);
}
}, [activeGroup]);
+
const getTemplatePreviewEditClickHandler = (templateName: string) => {
return () => {
const template = templatesToRender.find((template) => template.name === templateName);
@@ -163,6 +181,9 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
) : null}
);
+ const handleGoToTemplateSettingsCllick = () => {
+ getLocationSrv().update({ partial: true, query: { tab: 'Autoresolve' } });
+ };
return (
@@ -203,9 +224,20 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
key={activeTemplate.name}
className={cx('template-form', {
'template-form-full': true,
+ 'autoresolve-condition': selectedTemplateName && activeTemplate.name == 'resolve_condition_template',
})}
>
-
+
+ {activeTemplate.name == 'resolve_condition_template' && (
+
+ To activate autoresolving change integration
+
+
+ )}
void;
onUpdateTemplates?: () => void;
visible?: boolean;
+ selectedTemplateName?: string;
}
const AlertTemplatesFormContainer = observer((props: TeamEditContainerProps) => {
- const { alertReceiveChannelId, alertGroupId, onUpdateTemplates } = props;
+ const { alertReceiveChannelId, alertGroupId, onUpdateTemplates, selectedTemplateName } = props;
const store = useStore();
@@ -71,6 +72,7 @@ const AlertTemplatesFormContainer = observer((props: TeamEditContainerProps) =>
demoAlertEnabled={alertReceiveChannel?.demo_alert_enabled}
handleSendDemoAlertClick={handleSendDemoAlertClickCallback}
templatesRefreshing={templatesRefreshing}
+ selectedTemplateName={selectedTemplateName}
/>
);
});
diff --git a/grafana-plugin/src/containers/IntegrationSettings/IntegrationSettings.tsx b/grafana-plugin/src/containers/IntegrationSettings/IntegrationSettings.tsx
index b6572c23..e0192f1c 100644
--- a/grafana-plugin/src/containers/IntegrationSettings/IntegrationSettings.tsx
+++ b/grafana-plugin/src/containers/IntegrationSettings/IntegrationSettings.tsx
@@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
+import { getLocationSrv, setLocationSrv } from '@grafana/runtime';
import {
Drawer,
Tab,
@@ -47,6 +48,7 @@ interface IntegrationSettingsProps {
const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
const { id, onHide, onUpdate, onUpdateTemplates, startTab, alertGroupId } = props;
const [activeTab, setActiveTab] = useState(startTab || IntegrationSettingsTab.Templates);
+ const [selectedTemplate, setSelectedTemplate] = useState('');
const store = useStore();
@@ -57,6 +59,7 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
const getTabClickHandler = useCallback((tab: IntegrationSettingsTab) => {
return () => {
setActiveTab(tab);
+ getLocationSrv().update({ partial: true, query: { tab: tab } });
};
}, []);
@@ -64,9 +67,19 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
alertReceiveChannelStore.updateItem(id);
}, []);
+ useEffect(() => {
+ setActiveTab(startTab || IntegrationSettingsTab.Templates);
+ getLocationSrv().update({ partial: true, query: { tab: startTab || IntegrationSettingsTab.Templates } });
+ }, [startTab]);
+
const integration = alertReceiveChannelStore.getIntegration(alertReceiveChannel);
const [expanded, setExpanded] = useState(false);
+
+ const handleSwitchToTemplate = (templateName: string) => {
+ setSelectedTemplate(templateName);
+ };
+
return (
{
onUpdate={onUpdate}
onHide={onHide}
onUpdateTemplates={onUpdateTemplates}
+ selectedTemplateName={selectedTemplate}
/>
)}
{activeTab === IntegrationSettingsTab.Heartbeat && (
@@ -155,7 +169,13 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
)}
- {activeTab === IntegrationSettingsTab.Autoresolve && }
+ {activeTab === IntegrationSettingsTab.Autoresolve && (
+
+ )}
{/*{activeTab === IntegrationSettingsTab.LiveLogs && }*/}
{activeTab === IntegrationSettingsTab.HowToConnect && (
diff --git a/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.module.css b/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.module.css
index 6e0a3dd6..c3710fe4 100644
--- a/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.module.css
+++ b/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.module.css
@@ -13,7 +13,7 @@
}
.team-select {
- width: 300px;
+ width: 520px;
}
.team-select-actionbuttons {
@@ -28,3 +28,15 @@
.confirmation-buttons .save-team-button {
margin-left: 8px;
}
+
+.autoresolve-block {
+ height: 32px;
+ padding: 4px 8px;
+ margin-top: 8px;
+ min-width: 500px;
+ width: 520px;
+}
+
+.warning-icon-color {
+ color: var(--warning-text-color);
+}
diff --git a/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.tsx b/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.tsx
index 167e077e..4e9ea1f7 100644
--- a/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.tsx
+++ b/grafana-plugin/src/containers/IntegrationSettings/parts/Autoresolve.tsx
@@ -1,14 +1,17 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useState, useEffect } from 'react';
-import { Alert, Button, Label, Modal, Select } from '@grafana/ui';
+import { getLocationSrv } from '@grafana/runtime';
+import { Alert, Button, Icon, Label, Modal, Select } from '@grafana/ui';
import cn from 'classnames/bind';
import { get } from 'lodash-es';
import Block from 'components/GBlock/Block';
+import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import GSelect from 'containers/GSelect/GSelect';
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
+import { Alert as AlertType } from 'models/alertgroup/alertgroup.types';
import { Team } from 'models/team/team.types';
import { useStore } from 'state/useStore';
import { UserAction } from 'state/userAction';
@@ -20,9 +23,11 @@ const cx = cn.bind(styles);
interface AutoresolveProps {
alertReceiveChannelId: AlertReceiveChannel['id'];
+ alertGroupId?: AlertType['pk'];
+ onSwitchToTemplate?: (templateName: string) => void;
}
-const Autoresolve = ({ alertReceiveChannelId }: AutoresolveProps) => {
+const Autoresolve = ({ alertReceiveChannelId, onSwitchToTemplate, alertGroupId }: AutoresolveProps) => {
const store = useStore();
const { alertReceiveChannelStore, grafanaTeamStore, userStore } = store;
@@ -35,11 +40,36 @@ const Autoresolve = ({ alertReceiveChannelId }: AutoresolveProps) => {
const [autoresolveChanged, setAutoresolveChanged] = useState(false);
const [autoresolveValue, setAutoresolveValue] = useState(alertReceiveChannel?.allow_source_based_resolving);
const [showErrorOnTeamSelect, setShowErrorOnTeamSelect] = useState(false);
+ const [autoresolveSelected, setAutoresolveSelected] = useState(
+ alertReceiveChannel?.allow_source_based_resolving
+ );
+ const [autoresolveConditionInvalid, setAutoresolveConditionInvalid] = useState(false);
+
+ useEffect(() => {
+ store.alertReceiveChannelStore.updateItem(alertReceiveChannelId);
+ store.alertReceiveChannelStore.updateTemplates(alertReceiveChannelId, alertGroupId);
+ }, [alertGroupId, alertReceiveChannelId, store]);
+
+ useEffect(() => {
+ const autoresolveCondition = get(
+ store.alertReceiveChannelStore.templates[alertReceiveChannelId],
+ 'resolve_condition_template'
+ );
+ if (autoresolveCondition == ['invalid template']) {
+ setAutoresolveConditionInvalid(true);
+ }
+ }, [store.alertReceiveChannelStore.templates[alertReceiveChannelId]]);
const handleAutoresolveSelected = useCallback(
(autoresolveSelectedOption) => {
setAutoresolveChanged(true);
setAutoresolveValue(autoresolveSelectedOption?.value);
+ if (autoresolveSelectedOption?.value === 'true') {
+ setAutoresolveSelected(true);
+ }
+ if (autoresolveSelectedOption?.value === 'false') {
+ setAutoresolveSelected(false);
+ }
},
[autoresolveChanged]
);
@@ -84,6 +114,11 @@ const Autoresolve = ({ alertReceiveChannelId }: AutoresolveProps) => {
}
};
+ const handleGoToTemplateSettingsCllick = () => {
+ getLocationSrv().update({ partial: true, query: { tab: 'Templates' } });
+ onSwitchToTemplate('resolve_condition_template');
+ };
+
return (
<>
@@ -124,12 +159,39 @@ const Autoresolve = ({ alertReceiveChannelId }: AutoresolveProps) => {
defaultValue={{ value: 'true', label: 'Automatically resolve' }}
value={autoresolveValue.toString()}
options={[
- { value: 'true', label: 'Automatically resolve' },
+ { value: 'true', label: 'Resolve automatically' },
{ value: 'false', label: 'Resolve manually' },
]}
/>
+ {autoresolveSelected && (
+ <>
+
+
+
+ Incident will be automatically resolved when it matches{' '}
+
+
+
+
+ {autoresolveConditionInvalid && (
+
+
+
+ Autoresolving condition
+ template is invalid, please{' '}
+
+
+
+
+ )}
+ >
+ )}