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:
Yulia Shanyrova 2022-08-02 11:53:14 +02:00 committed by GitHub
parent 7deb6fb920
commit ca3090c94b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 158 additions and 14 deletions

View file

@ -49,3 +49,11 @@
.payloadExample {
margin-top: 24px;
}
.autoresolve-condition section {
border: 1px solid var(--primary-text-link);
}
.autoresolve-label {
margin-bottom: 0 !important;
}

View file

@ -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}

View file

@ -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}
/>
);
});

View file

@ -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">

View file

@ -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);
}

View file

@ -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}>

View file

@ -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 && (