refactoring: alert list improvments PR #10452

This commit is contained in:
Torkel Ödegaard
2018-01-10 11:54:47 +01:00
13 changed files with 393 additions and 235 deletions

View File

@@ -45,7 +45,7 @@ describe('AlertRuleList', () => {
it('should render 1 rule', () => {
page.update();
let ruleNode = page.find('.card-item-wrapper');
let ruleNode = page.find('.alert-rule-item');
expect(toJson(ruleNode)).toMatchSnapshot();
});

View File

@@ -5,6 +5,7 @@ import PageHeader from 'app/core/components/PageHeader/PageHeader';
import { IAlertRule } from 'app/stores/AlertListStore/AlertListStore';
import appEvents from 'app/core/app_events';
import IContainerProps from 'app/containers/IContainerProps';
import Highlighter from 'react-highlight-words';
@inject('view', 'nav', 'alertList')
@observer
@@ -23,11 +24,6 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
this.props.nav.load('alerting', 'alert-list');
this.fetchRules();
this.handleTooltipPositionChange = this.handleTooltipPositionChange.bind(this);
this.state = {
tooltipPosition: 'auto',
};
}
onStateFilterChanged = evt => {
@@ -49,12 +45,10 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
});
};
handleTooltipPositionChange(evt) {
evt.preventDefault();
this.setState({
tooltipPosition: evt.target.value,
});
}
onSearchQueryChange = evt => {
this.props.alertList.setSearchQuery(evt.target.value);
};
render() {
const { nav, alertList } = this.props;
@@ -63,8 +57,20 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
<PageHeader model={nav as any} />
<div className="page-container page-body">
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<label className="gf-form--has-input-icon gf-form--grow">
<input
type="text"
className="gf-form-input"
placeholder="Search alert"
value={alertList.search}
onChange={this.onSearchQueryChange}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
</div>
<div className="gf-form">
<label className="gf-form-label">Filter by state</label>
<label className="gf-form-label">States</label>
<div className="gf-form-select-wrapper width-13">
<select className="gf-form-input" onChange={this.onStateFilterChanged} value={alertList.stateFilter}>
@@ -80,8 +86,12 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
</a>
</div>
<section className="card-section card-list-layout-list">
<ol className="card-list">{alertList.rules.map(rule => <AlertRuleItem rule={rule} key={rule.id} />)}</ol>
<section>
<ol className="alert-rule-list">
{alertList.filteredRules.map(rule => (
<AlertRuleItem rule={rule} key={rule.id} search={alertList.search} />
))}
</ol>
</section>
</div>
</div>
@@ -99,6 +109,7 @@ function AlertStateFilterOption({ text, value }) {
export interface AlertRuleItemProps {
rule: IAlertRule;
search: string;
}
@observer
@@ -107,6 +118,16 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
this.props.rule.togglePaused();
};
renderText(text: string) {
return (
<Highlighter
highlightClassName="highlight-search-match"
textToHighlight={text}
searchWords={[this.props.search]}
/>
);
}
render() {
const { rule } = this.props;
@@ -119,36 +140,33 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
let ruleUrl = `dashboard/${rule.dashboardUri}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`;
return (
<li className="card-item-wrapper">
<div className="card-item card-item--alert">
<div className="card-item-header">
<div className="card-item-type">
<a
className="card-item-cog"
title="Pausing an alert rule prevents it from executing"
onClick={this.toggleState}
>
<i className={stateClass} />
</a>
<a className="card-item-cog" href={ruleUrl} title="Edit alert rule">
<i className="icon-gf icon-gf-settings" />
</a>
</div>
</div>
<div className="card-item-body">
<div className="card-item-details">
<div className="card-item-name">
<a href={ruleUrl}>{rule.name}</a>
</div>
<div className="card-item-sub-name">
<span className={`alert-list-item-state ${rule.stateClass}`}>
<i className={rule.stateIcon} /> {rule.stateText}
</span>
<span> for {rule.stateAge}</span>
</div>
{rule.info && <div className="small muted">{rule.info}</div>}
<li className="alert-rule-item">
<span className={`alert-rule-item__icon ${rule.stateClass}`}>
<i className={rule.stateIcon} />
</span>
<div className="alert-rule-item__body">
<div className="alert-rule-item__header">
<div className="alert-rule-item__name">
<a href={ruleUrl}>{this.renderText(rule.name)}</a>
</div>
<div className="alert-rule-item__text">
<span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span>
<span className="alert-rule-item__time"> for {rule.stateAge}</span>
</div>
</div>
{rule.info && <div className="small muted alert-rule-item__info">{this.renderText(rule.info)}</div>}
</div>
<div className="alert-rule-item__actions">
<a
className="btn btn-small btn-inverse alert-list__btn width-2"
title="Pausing an alert rule prevents it from executing"
onClick={this.toggleState}
>
<i className={stateClass} />
</a>
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
<i className="icon-gf icon-gf-settings" />
</a>
</div>
</li>
);

View File

@@ -2,71 +2,102 @@
exports[`AlertRuleList should render 1 rule 1`] = `
<li
className="card-item-wrapper"
className="alert-rule-item"
>
<span
className="alert-rule-item__icon alert-state-ok"
>
<i
className="icon-gf icon-gf-online"
/>
</span>
<div
className="card-item card-item--alert"
className="alert-rule-item__body"
>
<div
className="card-item-header"
className="alert-rule-item__header"
>
<div
className="card-item-type"
className="alert-rule-item__name"
>
<a
className="card-item-cog"
onClick={[Function]}
title="Pausing an alert rule prevents it from executing"
>
<i
className="fa fa-pause"
/>
</a>
<a
className="card-item-cog"
href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
title="Edit alert rule"
>
<i
className="icon-gf icon-gf-settings"
/>
<Highlighter
highlightClassName="highlight-search-match"
searchWords={
Array [
"",
]
}
textToHighlight="Panel Title alert"
>
<span>
<span
className=""
key="0"
>
Panel Title alert
</span>
</span>
</Highlighter>
</a>
</div>
</div>
<div
className="card-item-body"
>
<div
className="card-item-details"
className="alert-rule-item__text"
>
<div
className="card-item-name"
<span
className="alert-state-ok"
>
<a
href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
<Highlighter
highlightClassName="highlight-search-match"
searchWords={
Array [
"",
]
}
textToHighlight="OK"
>
Panel Title alert
</a>
</div>
<div
className="card-item-sub-name"
<span>
<span
className=""
key="0"
>
OK
</span>
</span>
</Highlighter>
</span>
<span
className="alert-rule-item__time"
>
<span
className="alert-list-item-state alert-state-ok"
>
<i
className="icon-gf icon-gf-online"
/>
OK
</span>
<span>
for
5 minutes
</span>
</div>
for
5 minutes
</span>
</div>
</div>
</div>
<div
className="alert-rule-item__actions"
>
<a
className="btn btn-small btn-inverse alert-list__btn width-2"
onClick={[Function]}
title="Pausing an alert rule prevents it from executing"
>
<i
className="fa fa-pause"
/>
</a>
<a
className="btn btn-small btn-inverse alert-list__btn width-2"
href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
title="Edit alert rule"
>
<i
className="icon-gf icon-gf-settings"
/>
</a>
</div>
</li>
`;

View File

@@ -138,10 +138,6 @@ function getAlertAnnotationInfo(ah) {
return 'Error: ' + ah.data.error;
}
if (ah.data.noData || ah.data.no_data) {
return 'No Data';
}
return '';
}

View File

@@ -12,7 +12,7 @@
<li ng-class="{active: ctrl.subTabIndex === 2}">
<a ng-click="ctrl.changeTabIndex(2)">State history</a>
</li>
<li>
<li>
<a ng-click="ctrl.delete()">Delete</a>
</li>
</ul>
@@ -143,36 +143,33 @@
<i>No state changes recorded</i>
</div>
<section class="card-section card-list-layout-list">
<ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="ah in ctrl.alertHistory">
<div class="alert-list card-item card-item--alert">
<div class="alert-list-body">
<div class="alert-list-icon alert-list-item-state {{ah.stateModel.stateClass}}">
<i class="{{ah.stateModel.iconClass}}"></i>
</div>
<div class="alert-list-main alert-list-text">
<span class="alert-list-state {{ah.stateModel.stateClass}}">{{ah.stateModel.text}}</span>
<span class="alert-list-info">{{ah.info}}</span>
</div>
</div>
<div class="alert-list-footer alert-list-text">
<span>{{ah.time}}</span>
<span><!--Img Link--></span>
</div>
</div>
</li>
</ol>
</section>
</div>
</div>
<ol class="alert-rule-list" >
<li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
<div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
<i class="{{al.stateModel.iconClass}}"></i>
</div>
<div class="alert-rule-item__body">
<div class="alert-rule-item__header">
<div class="alert-rule-item__text">
<span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
</div>
</div>
<span class="alert-list-info">{{al.info}}</span>
</div>
<div class="alert-rule-item__time">
<span>{{al.time}}</span>
</div>
</li>
</ol>
</div>
</div>
</div>
<div class="gf-form-group" ng-if="!ctrl.alert">
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.enable()">
<i class="icon-gf icon-gf-alert"></i>
Create Alert
</button>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.enable()">
<i class="icon-gf icon-gf-alert"></i>
Create Alert
</button>
</div>
</div>

View File

@@ -3,24 +3,22 @@
{{ctrl.noAlertsMessage}}
</div>
<section class="card-section card-list-layout-list" ng-if="ctrl.panel.show === 'current'">
<ol class="card-list">
<li class="card-item-wrapper" ng-repeat="alert in ctrl.currentAlerts">
<div class="alert-list card-item card-item--alert">
<div class="alert-list-body">
<div class="alert-list-icon alert-list-item-state {{alert.stateModel.stateClass}}">
<i class="{{alert.stateModel.iconClass}}"></i>
</div>
<div class="alert-list-main">
<p class="alert-list-title">
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
{{alert.name}}
</a>
</p>
<p class="alert-list-text">
<span class="alert-list-state {{alert.stateModel.stateClass}}">{{alert.stateModel.text}}</span>
for {{alert.newStateDateAgo}}
</p>
<section ng-if="ctrl.panel.show === 'current'">
<ol class="alert-rule-list">
<li class="alert-rule-item" ng-repeat="alert in ctrl.currentAlerts">
<div class="alert-rule-item__body">
<div class="alert-rule-item__icon {{alert.stateModel.stateClass}}">
<i class="{{alert.stateModel.iconClass}}"></i>
</div>
<div class="alert-rule-item__header">
<p class="alert-rule-item__name">
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
{{alert.name}}
</a>
</p>
<div class="alert-rule-item__text">
<span class="{{alert.stateModel.stateClass}}">{{alert.stateModel.text}}</span>
<span class="alert-rule-item__time">for {{alert.newStateDateAgo}}</span>
</div>
</div>
</div>
@@ -28,30 +26,25 @@
</ol>
</section>
<section class="card-section card-list-layout-list" ng-if="ctrl.panel.show === 'changes'">
<ol class="card-list">
<li class="card-item-wrapper" ng-repeat="al in ctrl.alertHistory">
<div class="alert-list card-item card-item--alert">
<div class="alert-list-body">
<div class="alert-list-icon alert-list-item-state {{al.stateModel.stateClass}}">
<i class="{{al.stateModel.iconClass}}"></i>
</div>
<div class="alert-list-main">
<p class="alert-list-title">{{al.alertName}}</p>
<div class="alert-list-text">
<span class="alert-list-state {{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
<span class="alert-list-info alert-list-info-left">{{al.info}}</span>
</div>
</div>
</div>
<div class="alert-list-footer">
<span class="alert-list-text">{{al.time}}</span>
<span class="alert-list-text">
<!--Img Link-->
</span>
</div>
</div>
</li>
</ol>
</section>
<section ng-if="ctrl.panel.show === 'changes'">
<ol class="alert-rule-list">
<li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
<div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
<i class="{{al.stateModel.iconClass}}"></i>
</div>
<div class="alert-rule-item__body">
<div class="alert-rule-item__header">
<p class="alert-rule-item__name">{{al.alertName}}</p>
<div class="alert-rule-item__text">
<span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
</div>
</div>
<span class="alert-rule-item__info">{{al.info}}</span>
</div>
<div class="alert-rule-item__time">
<span>{{al.time}}</span>
</div>
</li>
</ol>
</section>
</div>

View File

@@ -0,0 +1,65 @@
import { AlertListStore } from './AlertListStore';
import { backendSrv } from 'test/mocks/common';
import moment from 'moment';
function getRule(name, state, info) {
return {
id: 11,
dashboardId: 58,
panelId: 3,
name: name,
state: state,
newStateDate: moment()
.subtract(5, 'minutes')
.format(),
evalData: {},
executionError: '',
dashboardUri: 'db/mygool',
stateText: state,
stateIcon: 'fa',
stateClass: 'asd',
stateAge: '10m',
info: info,
};
}
describe('AlertListStore', () => {
let store;
beforeAll(() => {
store = AlertListStore.create(
{
rules: [
getRule('Europe', 'OK', 'backend-01'),
getRule('Google', 'ALERTING', 'backend-02'),
getRule('Amazon', 'PAUSED', 'backend-03'),
getRule('West-Europe', 'PAUSED', 'backend-03'),
],
search: '',
},
{
backendSrv: backendSrv,
}
);
});
it('search should filter list on name', () => {
store.setSearchQuery('urope');
expect(store.filteredRules).toHaveLength(2);
});
it('search should filter list on state', () => {
store.setSearchQuery('ale');
expect(store.filteredRules).toHaveLength(1);
});
it('search should filter list on info', () => {
store.setSearchQuery('-0');
expect(store.filteredRules).toHaveLength(4);
});
it('search should be equal', () => {
store.setSearchQuery('alert');
expect(store.search).toBe('alert');
});
});

View File

@@ -9,7 +9,16 @@ export const AlertListStore = types
.model('AlertListStore', {
rules: types.array(AlertRule),
stateFilter: types.optional(types.string, 'all'),
search: types.optional(types.string, ''),
})
.views(self => ({
get filteredRules() {
let regex = new RegExp(self.search, 'i');
return self.rules.filter(alert => {
return regex.test(alert.name) || regex.test(alert.stateText) || regex.test(alert.info);
});
},
}))
.actions(self => ({
loadRules: flow(function* load(filters) {
const backendSrv = getEnv(self).backendSrv;
@@ -31,4 +40,7 @@ export const AlertListStore = types
self.rules.push(AlertRule.create(rule));
}
}),
setSearchQuery(query: string) {
self.search = query;
},
}));