Autoresolve condition select is linked with alert template tab (#309)
* Autoresolve condition select is linked with alert template tab * Linting * Linting Co-authored-by: Matvey Kukuy <motakuk@gmail.com>
This commit is contained in:
parent
7deb6fb920
commit
ca3090c94b
7 changed files with 158 additions and 14 deletions
|
|
@ -49,3 +49,11 @@
|
|||
.payloadExample {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.autoresolve-condition section {
|
||||
border: 1px solid var(--primary-text-link);
|
||||
}
|
||||
|
||||
.autoresolve-label {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
const handleGoToTemplateSettingsCllick = () => {
|
||||
getLocationSrv().update({ partial: true, query: { tab: 'Autoresolve' } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
|
|
@ -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',
|
||||
})}
|
||||
>
|
||||
<Label>{getLabelFromTemplateName(activeTemplate.name, activeGroup)}</Label>
|
||||
<Label className={cx({ 'autoresolve-label': activeTemplate.name == 'resolve_condition_template' })}>
|
||||
{getLabelFromTemplateName(activeTemplate.name, activeGroup)}
|
||||
</Label>
|
||||
{activeTemplate.name == 'resolve_condition_template' && (
|
||||
<Text type="secondary" size="small">
|
||||
To activate autoresolving change integration
|
||||
<Button fill="text" size="sm" onClick={handleGoToTemplateSettingsCllick}>
|
||||
settings
|
||||
</Button>
|
||||
</Text>
|
||||
)}
|
||||
<MonacoJinja2Editor
|
||||
value={tempValues[activeTemplate.name] ?? (templates[activeTemplate.name] || '')}
|
||||
disabled={false}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@ interface TeamEditContainerProps {
|
|||
onUpdate?: () => 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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<IntegrationSettingsTab>(startTab || IntegrationSettingsTab.Templates);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<string>('');
|
||||
|
||||
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 (
|
||||
<Drawer
|
||||
scrollableContent
|
||||
|
|
@ -148,6 +161,7 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
onUpdate={onUpdate}
|
||||
onHide={onHide}
|
||||
onUpdateTemplates={onUpdateTemplates}
|
||||
selectedTemplateName={selectedTemplate}
|
||||
/>
|
||||
)}
|
||||
{activeTab === IntegrationSettingsTab.Heartbeat && (
|
||||
|
|
@ -155,7 +169,13 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
<HeartbeatForm alertReceveChannelId={id} onUpdate={onUpdate} />
|
||||
</div>
|
||||
)}
|
||||
{activeTab === IntegrationSettingsTab.Autoresolve && <Autoresolve alertReceiveChannelId={id} />}
|
||||
{activeTab === IntegrationSettingsTab.Autoresolve && (
|
||||
<Autoresolve
|
||||
alertReceiveChannelId={id}
|
||||
onSwitchToTemplate={handleSwitchToTemplate}
|
||||
alertGroupId={alertGroupId}
|
||||
/>
|
||||
)}
|
||||
{/*{activeTab === IntegrationSettingsTab.LiveLogs && <LiveLogs alertReceiveChannelId={id} />}*/}
|
||||
{activeTab === IntegrationSettingsTab.HowToConnect && (
|
||||
<div className="container">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<boolean>(false);
|
||||
const [autoresolveValue, setAutoresolveValue] = useState<boolean>(alertReceiveChannel?.allow_source_based_resolving);
|
||||
const [showErrorOnTeamSelect, setShowErrorOnTeamSelect] = useState<boolean>(false);
|
||||
const [autoresolveSelected, setAutoresolveSelected] = useState<boolean>(
|
||||
alertReceiveChannel?.allow_source_based_resolving
|
||||
);
|
||||
const [autoresolveConditionInvalid, setAutoresolveConditionInvalid] = useState<boolean>(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 (
|
||||
<>
|
||||
<Block>
|
||||
|
|
@ -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' },
|
||||
]}
|
||||
/>
|
||||
</WithPermissionControl>
|
||||
</div>
|
||||
{autoresolveSelected && (
|
||||
<>
|
||||
<Block shadowed bordered className={cx('autoresolve-block')}>
|
||||
<div>
|
||||
<Text type="secondary" size="small">
|
||||
<Icon name="info-circle" /> Incident will be automatically resolved when it matches{' '}
|
||||
</Text>
|
||||
<Button fill="text" size="sm" onClick={handleGoToTemplateSettingsCllick}>
|
||||
autoresolve condition
|
||||
</Button>
|
||||
</div>
|
||||
</Block>
|
||||
{autoresolveConditionInvalid && (
|
||||
<Block shadowed bordered className={cx('autoresolve-block')}>
|
||||
<div>
|
||||
<Text type="secondary" size="small">
|
||||
<Icon name="exclamation-triangle" className={cx('warning-icon-color')} /> Autoresolving condition
|
||||
template is invalid, please{' '}
|
||||
</Text>
|
||||
<Button fill="text" size="sm" onClick={handleGoToTemplateSettingsCllick}>
|
||||
Edit it
|
||||
</Button>
|
||||
</div>
|
||||
</Block>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={cx('team-select-actionbuttons')}>
|
||||
<Button variant="primary" onClick={handleSaveClick}>
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
`Integration with id=${query?.id} is not found. Please select integration from the list.`
|
||||
);
|
||||
}
|
||||
if (query.tab) {
|
||||
this.setState({ integrationSettingsTab: query.tab });
|
||||
this.setState({ alertReceiveChannelToShowSettings: query.id });
|
||||
}
|
||||
}
|
||||
if (!selectedAlertReceiveChannel) {
|
||||
selectedAlertReceiveChannel = searchResult[0]?.id;
|
||||
|
|
@ -90,6 +94,9 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
if (this.props.query.id !== prevProps.query.id) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
if (this.props.query.tab !== prevProps.query.tab) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -200,12 +207,13 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
}}
|
||||
startTab={integrationSettingsTab}
|
||||
id={alertReceiveChannelToShowSettings}
|
||||
onHide={() =>
|
||||
onHide={() => {
|
||||
this.setState({
|
||||
alertReceiveChannelToShowSettings: undefined,
|
||||
integrationSettingsTab: undefined,
|
||||
})
|
||||
}
|
||||
});
|
||||
getLocationSrv().update({ partial: true, query: { tab: undefined } });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showCreateIntegrationModal && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue