Commit 2de57f09 by Torkel Ödegaard Committed by GitHub

Merge pull request #14707 from grafana/14388/alert-tab-ux-update

Alert tab ux update
parents bb15eb30 e0c28ba7
......@@ -24,12 +24,14 @@ class EmptyListCTA extends Component<Props, any> {
<i className={buttonIcon} />
{buttonTitle}
</a>
<div className="empty-list-cta__pro-tip">
<i className="fa fa-rocket" /> ProTip: {proTip}
<a className="text-link empty-list-cta__pro-tip-link" href={proTipLink} target={proTipTarget}>
{proTipLinkTitle}
</a>
</div>
{proTip && (
<div className="empty-list-cta__pro-tip">
<i className="fa fa-rocket" /> ProTip: {proTip}
<a className="text-link empty-list-cta__pro-tip-link" href={proTipLink} target={proTipTarget}>
{proTipLinkTitle}
</a>
</div>
)}
</div>
);
}
......
......@@ -45,6 +45,7 @@ export class AlertTabCtrl {
this.noDataModes = alertDef.noDataModes;
this.executionErrorModes = alertDef.executionErrorModes;
this.appSubUrl = config.appSubUrl;
this.panelCtrl._enableAlert = this.enable;
}
$onInit() {
......@@ -114,7 +115,7 @@ export class AlertTabCtrl {
}
getNotifications() {
return Promise.resolve(
return this.$q.when(
this.notifications.map(item => {
return this.uiSegmentSrv.newSegment(item.name);
})
......@@ -147,6 +148,7 @@ export class AlertTabCtrl {
// reset plus button
this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
this.addNotificationSegment.html = this.uiSegmentSrv.newPlusButton().html;
this.addNotificationSegment.fake = true;
}
removeNotification(index) {
......@@ -353,11 +355,11 @@ export class AlertTabCtrl {
});
}
enable() {
enable = () => {
this.panel.alert = {};
this.initModel();
this.panel.alert.for = '5m'; //default value for new alerts. for existing alerts we use 0m to avoid breaking changes
}
};
evaluatorParamsChanged() {
ThresholdMapper.alertToGraphThresholds(this.panel);
......
// Libraries
import React, { PureComponent } from 'react';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
// Services & Utils
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
import appEvents from 'app/core/app_events';
// Components
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import StateHistory from './StateHistory';
import 'app/features/alerting/AlertTabCtrl';
// Types
import { DashboardModel } from '../dashboard_model';
import { PanelModel } from '../panel_model';
interface Props {
angularPanel?: AngularComponent;
dashboard: DashboardModel;
panel: PanelModel;
}
export class AlertTab extends PureComponent<Props> {
element: any;
component: AngularComponent;
constructor(props) {
super(props);
}
panelCtrl: any;
componentDidMount() {
if (this.shouldLoadAlertTab()) {
......@@ -29,7 +39,7 @@ export class AlertTab extends PureComponent<Props> {
}
shouldLoadAlertTab() {
return this.props.angularPanel && this.element;
return this.props.angularPanel && this.element && !this.component;
}
componentWillUnmount() {
......@@ -51,21 +61,80 @@ export class AlertTab extends PureComponent<Props> {
return;
}
const panelCtrl = scope.$$childHead.ctrl;
this.panelCtrl = scope.$$childHead.ctrl;
const loader = getAngularLoader();
const template = '<alert-tab />';
const scopeProps = {
ctrl: panelCtrl,
ctrl: this.panelCtrl,
};
this.component = loader.load(this.element, scopeProps, template);
}
stateHistory = (): EditorToolbarView => {
return {
title: 'State history',
render: () => {
return (
<StateHistory
dashboard={this.props.dashboard}
panelId={this.props.panel.id}
onRefresh={this.panelCtrl.refresh}
/>
);
},
};
};
deleteAlert = (): EditorToolbarView => {
const { panel } = this.props;
return {
title: 'Delete',
btnType: 'danger',
onClick: () => {
appEvents.emit('confirm-modal', {
title: 'Delete Alert',
text: 'Are you sure you want to delete this alert rule?',
text2: 'You need to save dashboard for the delete to take effect',
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: () => {
delete panel.alert;
panel.thresholds = [];
this.panelCtrl.alertState = null;
this.panelCtrl.render();
this.forceUpdate();
},
});
},
};
};
onAddAlert = () => {
this.panelCtrl._enableAlert();
this.component.digest();
this.forceUpdate();
};
render() {
const { alert } = this.props.panel;
const toolbarItems = alert ? [this.stateHistory(), this.deleteAlert()] : [];
const model = {
title: 'Panel has no alert rule defined',
icon: 'icon-gf icon-gf-alert',
onClick: this.onAddAlert,
buttonTitle: 'Create Alert',
};
return (
<EditorTabBody heading="Alert" toolbarItems={[]}>
<div ref={element => (this.element = element)} />
<EditorTabBody heading="Alert" toolbarItems={toolbarItems}>
<>
<div ref={element => (this.element = element)} />
{!alert && <EmptyListCTA model={model} />}
</>
</EditorTabBody>
);
}
......
......@@ -10,21 +10,22 @@ interface Props {
children: JSX.Element;
heading: string;
renderToolbar?: () => JSX.Element;
toolbarItems?: EditorToolBarView[];
toolbarItems?: EditorToolbarView[];
}
export interface EditorToolBarView {
export interface EditorToolbarView {
title?: string;
heading?: string;
imgSrc?: string;
icon?: string;
disabled?: boolean;
onClick?: () => void;
render: (closeFunction?: any) => JSX.Element | JSX.Element[];
render?: () => JSX.Element;
action?: () => void;
btnType?: 'danger';
}
interface State {
openView?: EditorToolBarView;
openView?: EditorToolbarView;
isOpen: boolean;
fadeIn: boolean;
}
......@@ -48,7 +49,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
this.setState({ fadeIn: true });
}
onToggleToolBarView = (item: EditorToolBarView) => {
onToggleToolBarView = (item: EditorToolbarView) => {
this.setState({
openView: item,
isOpen: !this.state.isOpen,
......@@ -74,12 +75,15 @@ export class EditorTabBody extends PureComponent<Props, State> {
return state;
}
renderButton(view: EditorToolBarView) {
renderButton(view: EditorToolbarView) {
const onClick = () => {
if (view.onClick) {
view.onClick();
}
this.onToggleToolBarView(view);
if (view.render) {
this.onToggleToolBarView(view);
}
};
return (
......@@ -91,7 +95,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
);
}
renderOpenView(view: EditorToolBarView) {
renderOpenView(view: EditorToolbarView) {
return (
<PanelOptionSection title={view.title || view.heading} onClose={this.onCloseOpenView}>
{view.render()}
......
......@@ -54,7 +54,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
case 'queries':
return <QueriesTab panel={panel} dashboard={dashboard} />;
case 'alert':
return <AlertTab angularPanel={angularPanel} />;
return <AlertTab angularPanel={angularPanel} dashboard={dashboard} panel={panel} />;
case 'visualization':
return (
<VisualizationTab
......
// Libraries
import React, { SFC, PureComponent } from 'react';
import React, { PureComponent, SFC } from 'react';
import _ from 'lodash';
// Components
import './../../panel/metrics_tab';
import { EditorTabBody } from './EditorTabBody';
import 'app/features/panel/metrics_tab';
import { EditorTabBody, EditorToolbarView} from './EditorTabBody';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { QueryInspector } from './QueryInspector';
import { QueryOptions } from './QueryOptions';
......@@ -13,14 +13,14 @@ import { PanelOptionSection } from './PanelOptionSection';
// Services
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv';
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
import config from 'app/core/config';
// Types
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { DataSourceSelectItem, DataQuery } from 'app/types';
import { DataQuery, DataSourceSelectItem } from 'app/types';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
interface Props {
......@@ -204,12 +204,12 @@ export class QueriesTab extends PureComponent<Props, State> {
const { panel } = this.props;
const { currentDS, isAddingMixed } = this.state;
const queryInspector = {
const queryInspector: EditorToolbarView = {
title: 'Query Inspector',
render: this.renderQueryInspector,
};
const dsHelp = {
const dsHelp: EditorToolbarView = {
heading: 'Help',
icon: 'fa fa-question',
render: this.renderHelp,
......
import React, { PureComponent } from 'react';
import alertDef from '../../alerting/state/alertDef';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { DashboardModel } from '../dashboard_model';
import appEvents from '../../../core/app_events';
interface Props {
dashboard: DashboardModel;
panelId: number;
onRefresh: () => void;
}
interface State {
stateHistoryItems: any[];
}
class StateHistory extends PureComponent<Props, State> {
state = {
stateHistoryItems: [],
};
componentDidMount(): void {
const { dashboard, panelId } = this.props;
getBackendSrv()
.get(`/api/annotations?dashboardId=${dashboard.id}&panelId=${panelId}&limit=50&type=alert`)
.then(res => {
const items = res.map(item => {
return {
stateModel: alertDef.getStateDisplayModel(item.newState),
time: dashboard.formatDate(item.time, 'MMM D, YYYY HH:mm:ss'),
info: alertDef.getAlertAnnotationInfo(item),
};
});
this.setState({
stateHistoryItems: items,
});
});
}
clearHistory = () => {
const { dashboard, onRefresh, panelId } = this.props;
appEvents.emit('confirm-modal', {
title: 'Delete Alert History',
text: 'Are you sure you want to remove all history & annotations for this alert?',
icon: 'fa-trash',
yesText: 'Yes',
onConfirm: () => {
getBackendSrv()
.post('/api/annotations/mass-delete', {
dashboardId: dashboard.id,
panelId: panelId,
})
.then(() => {
onRefresh();
});
this.setState({
stateHistoryItems: [],
});
},
});
};
render() {
const { stateHistoryItems } = this.state;
return (
<div>
{stateHistoryItems.length > 0 && (
<div className="p-b-1">
<span className="muted">Last 50 state changes</span>
<button className="btn btn-mini btn-danger pull-right" onClick={this.clearHistory}>
<i className="fa fa-trash" /> {` Clear history`}
</button>
</div>
)}
<ol className="alert-rule-list">
{stateHistoryItems.length > 0 ? (
stateHistoryItems.map((item, index) => {
return (
<li className="alert-rule-item" key={`${item.time}-${index}`}>
<div className={`alert-rule-item__icon ${item.stateModel.stateClass}`}>
<i className={item.stateModel.iconClass} />
</div>
<div className="alert-rule-item__body">
<div className="alert-rule-item__header">
<p className="alert-rule-item__name">{item.alertName}</p>
<div className="alert-rule-item__text">
<span className={`${item.stateModel.stateClass}`}>{item.stateModel.text}</span>
</div>
</div>
{item.info}
</div>
<div className="alert-rule-item__time">{item.time}</div>
</li>
);
})
) : (
<i>No state changes recorded</i>
)}
</ol>
</div>
);
}
}
export default StateHistory;
......@@ -2,10 +2,10 @@
import React, { PureComponent } from 'react';
// Utils & Services
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
// Components
import { EditorTabBody } from './EditorTabBody';
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
import { VizTypePicker } from './VizTypePicker';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
import { FadeIn } from 'app/core/components/Animations/FadeIn';
......@@ -206,7 +206,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
const { plugin } = this.props;
const { isVizPickerOpen, searchQuery } = this.state;
const pluginHelp = {
const pluginHelp: EditorToolbarView = {
heading: 'Help',
icon: 'fa fa-question',
render: this.renderHelp,
......
......@@ -107,6 +107,7 @@
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
overflow: hidden;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment