wip: redux refactor

This commit is contained in:
Torkel Ödegaard
2018-08-31 09:42:32 -07:00
parent d68007fde3
commit 593cc5380f
9 changed files with 111 additions and 28 deletions

View 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;
}
};

View File

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

View 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));

View File

@@ -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>
`;