mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
wip: redux refactor
This commit is contained in:
52
public/app/features/alerting/apis/index.ts
Normal file
52
public/app/features/alerting/apis/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import alertDef from '../alert_def';
|
||||
import moment from 'moment';
|
||||
|
||||
export interface AlertRule {
|
||||
id: number;
|
||||
dashboardId: number;
|
||||
panelId: number;
|
||||
name: string;
|
||||
state: string;
|
||||
stateText: string;
|
||||
stateIcon: string;
|
||||
stateClass: string;
|
||||
stateAge: string;
|
||||
info?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function setStateFields(rule, state) {
|
||||
const stateModel = alertDef.getStateDisplayModel(state);
|
||||
rule.state = state;
|
||||
rule.stateText = stateModel.text;
|
||||
rule.stateIcon = stateModel.iconClass;
|
||||
rule.stateClass = stateModel.stateClass;
|
||||
rule.stateAge = moment(rule.newStateDate)
|
||||
.fromNow()
|
||||
.replace(' ago', '');
|
||||
}
|
||||
|
||||
export const getAlertRules = async (): Promise<AlertRule[]> => {
|
||||
try {
|
||||
const rules = await getBackendSrv().get('/api/alerts', {});
|
||||
|
||||
for (const rule of rules) {
|
||||
setStateFields(rule, rule.state);
|
||||
|
||||
if (rule.state !== 'paused') {
|
||||
if (rule.executionError) {
|
||||
rule.info = 'Execution Error: ' + rule.executionError;
|
||||
}
|
||||
if (rule.evalData && rule.evalData.noData) {
|
||||
rule.info = 'Query returned no data';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { AlertRuleList } from './AlertRuleList';
|
||||
import { RootStore } from 'app/stores/RootStore/RootStore';
|
||||
import { backendSrv, createNavTree } from 'test/mocks/common';
|
||||
import { mount } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
|
||||
describe('AlertRuleList', () => {
|
||||
let page, store;
|
||||
|
||||
beforeAll(() => {
|
||||
backendSrv.get.mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 11,
|
||||
dashboardId: 58,
|
||||
panelId: 3,
|
||||
name: 'Panel Title alert',
|
||||
state: 'ok',
|
||||
newStateDate: moment()
|
||||
.subtract(5, 'minutes')
|
||||
.format(),
|
||||
evalData: {},
|
||||
executionError: '',
|
||||
url: 'd/ufkcofof/my-goal',
|
||||
canEdit: true,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
store = RootStore.create(
|
||||
{},
|
||||
{
|
||||
backendSrv: backendSrv,
|
||||
navTree: createNavTree('alerting', 'alert-list'),
|
||||
}
|
||||
);
|
||||
|
||||
page = mount(<AlertRuleList {...store} />);
|
||||
});
|
||||
|
||||
it('should call api to get rules', () => {
|
||||
expect(backendSrv.get.mock.calls[0][0]).toEqual('/api/alerts');
|
||||
});
|
||||
|
||||
it('should render 1 rule', () => {
|
||||
page.update();
|
||||
const ruleNode = page.find('.alert-rule-item');
|
||||
expect(toJson(ruleNode)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('toggle state should change pause rule if not paused', async () => {
|
||||
backendSrv.post.mockReturnValue(
|
||||
Promise.resolve({
|
||||
state: 'paused',
|
||||
})
|
||||
);
|
||||
|
||||
page.find('.fa-pause').simulate('click');
|
||||
|
||||
// wait for api call to resolve
|
||||
await Promise.resolve();
|
||||
page.update();
|
||||
|
||||
expect(store.alertList.rules[0].state).toBe('paused');
|
||||
expect(page.find('.fa-play')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
209
public/app/features/alerting/containers/AlertRuleList.tsx
Normal file
209
public/app/features/alerting/containers/AlertRuleList.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import { initNav } from 'app/core/actions';
|
||||
import { ContainerProps } from 'app/types';
|
||||
import { getAlertRules, AlertRule } from '../apis';
|
||||
|
||||
interface Props extends ContainerProps {}
|
||||
|
||||
interface State {
|
||||
rules: AlertRule[];
|
||||
search: string;
|
||||
stateFilter: string;
|
||||
}
|
||||
|
||||
export class AlertRuleList extends PureComponent<Props, State> {
|
||||
stateFilters = [
|
||||
{ text: 'All', value: 'all' },
|
||||
{ text: 'OK', value: 'ok' },
|
||||
{ text: 'Not OK', value: 'not_ok' },
|
||||
{ text: 'Alerting', value: 'alerting' },
|
||||
{ text: 'No Data', value: 'no_data' },
|
||||
{ text: 'Paused', value: 'paused' },
|
||||
];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rules: [],
|
||||
search: '',
|
||||
stateFilter: '',
|
||||
};
|
||||
|
||||
this.props.initNav('alerting', 'alert-list');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchRules();
|
||||
}
|
||||
|
||||
onStateFilterChanged = evt => {
|
||||
// this.props.view.updateQuery({ state: evt.target.value });
|
||||
// this.fetchRules();
|
||||
};
|
||||
|
||||
async fetchRules() {
|
||||
try {
|
||||
const rules = await getAlertRules();
|
||||
this.setState({ rules });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// this.props.alertList.loadRules({
|
||||
// state: this.props.view.query.get('state') || 'all',
|
||||
// });
|
||||
}
|
||||
|
||||
onOpenHowTo = () => {
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
||||
modalClass: 'confirm-modal',
|
||||
model: {},
|
||||
});
|
||||
};
|
||||
|
||||
onSearchQueryChange = evt => {
|
||||
// this.props.alertList.setSearchQuery(evt.target.value);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navModel } = this.props;
|
||||
const { rules, search, stateFilter } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
<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 alerts"
|
||||
value={search}
|
||||
onChange={this.onSearchQueryChange}
|
||||
/>
|
||||
<i className="gf-form-input-icon fa fa-search" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label">States</label>
|
||||
|
||||
<div className="gf-form-select-wrapper width-13">
|
||||
<select className="gf-form-input" onChange={this.onStateFilterChanged} value={stateFilter}>
|
||||
{this.stateFilters.map(AlertStateFilterOption)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="page-action-bar__spacer" />
|
||||
|
||||
<a className="btn btn-secondary" onClick={this.onOpenHowTo}>
|
||||
<i className="fa fa-info-circle" /> How to add an alert
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<ol className="alert-rule-list">
|
||||
{rules.map(rule => (
|
||||
<AlertRuleItem rule={rule} key={rule.id} search={search} />
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function AlertStateFilterOption({ text, value }) {
|
||||
return (
|
||||
<option key={value} value={value}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
export interface AlertRuleItemProps {
|
||||
rule: AlertRule;
|
||||
search: string;
|
||||
}
|
||||
|
||||
export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
|
||||
toggleState = () => {
|
||||
// this.props.rule.togglePaused();
|
||||
};
|
||||
|
||||
renderText(text: string) {
|
||||
return (
|
||||
<Highlighter
|
||||
highlightClassName="highlight-search-match"
|
||||
textToHighlight={text}
|
||||
searchWords={[this.props.search]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { rule } = this.props;
|
||||
|
||||
const stateClass = classNames({
|
||||
fa: true,
|
||||
'fa-play': rule.state === 'paused',
|
||||
'fa-pause': rule.state !== 'paused',
|
||||
});
|
||||
|
||||
const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen=true&edit=true&tab=alert`;
|
||||
|
||||
return (
|
||||
<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">
|
||||
<button
|
||||
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} />
|
||||
</button>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
navModel: state.navModel,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initNav,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(AlertRuleList));
|
||||
@@ -0,0 +1,103 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AlertRuleList should render 1 rule 1`] = `
|
||||
<li
|
||||
className="alert-rule-item"
|
||||
>
|
||||
<span
|
||||
className="alert-rule-item__icon alert-state-ok"
|
||||
>
|
||||
<i
|
||||
className="icon-gf icon-gf-online"
|
||||
/>
|
||||
</span>
|
||||
<div
|
||||
className="alert-rule-item__body"
|
||||
>
|
||||
<div
|
||||
className="alert-rule-item__header"
|
||||
>
|
||||
<div
|
||||
className="alert-rule-item__name"
|
||||
>
|
||||
<a
|
||||
href="d/ufkcofof/my-goal?panelId=3&fullscreen=true&edit=true&tab=alert"
|
||||
>
|
||||
<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
|
||||
className="alert-rule-item__text"
|
||||
>
|
||||
<span
|
||||
className="alert-state-ok"
|
||||
>
|
||||
<Highlighter
|
||||
highlightClassName="highlight-search-match"
|
||||
searchWords={
|
||||
Array [
|
||||
"",
|
||||
]
|
||||
}
|
||||
textToHighlight="OK"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
className=""
|
||||
key="0"
|
||||
>
|
||||
OK
|
||||
</span>
|
||||
</span>
|
||||
</Highlighter>
|
||||
</span>
|
||||
<span
|
||||
className="alert-rule-item__time"
|
||||
>
|
||||
for
|
||||
5 minutes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="alert-rule-item__actions"
|
||||
>
|
||||
<button
|
||||
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"
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
className="btn btn-small btn-inverse alert-list__btn width-2"
|
||||
href="d/ufkcofof/my-goal?panelId=3&fullscreen=true&edit=true&tab=alert"
|
||||
title="Edit alert rule"
|
||||
>
|
||||
<i
|
||||
className="icon-gf icon-gf-settings"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
Reference in New Issue
Block a user