Refactor KapacitorRulePage to work for new and existing rules, and add new rule button

pull/10616/head
Will Piers 2016-11-04 16:39:19 -07:00
parent a98a533f93
commit 20b5c96e29
9 changed files with 150 additions and 74 deletions

View File

@ -37,9 +37,9 @@ export default function queryConfigs(state = {}, action) {
}
case 'LOAD_KAPACITOR_QUERY': {
const {queryID, query} = action.payload;
const {query} = action.payload;
const nextState = Object.assign({}, state, {
[queryID]: query,
[query.id]: query,
});
return nextState;

View File

@ -119,6 +119,7 @@ const Root = React.createClass({
<Route path="alerts" component={AlertsApp} />
<Route path="alert-rules" component={KapacitorRulesPage} />
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
<Route path="alert-rules/new" component={KapacitorRulePage} />
</Route>
<Route path="tasks" component={TasksPage} />
<Route path="*" component={NotFound} />

View File

@ -1,14 +1,38 @@
import uuid from 'node-uuid';
import {getRules, getRule} from 'src/kapacitor/apis';
import {getKapacitor} from 'src/shared/apis';
export function fetchRule() { // ruleID
export function fetchRule(source, ruleID) {
return (dispatch) => {
// do some ajax to get the rule. put it in the payload
const queryID = uuid.v4();
getKapacitor(source).then((kapacitor) => {
getRule(kapacitor, ruleID).then(({data: rule}) => {
dispatch({
type: 'LOAD_RULE',
payload: {},
payload: {
rule: Object.assign(rule, {queryID: rule.query.id}),
},
});
dispatch({
type: 'LOAD_KAPACITOR_QUERY',
payload: {
query: rule.query,
},
});
});
});
};
}
export function loadDefaultRule() {
return (dispatch) => {
const queryID = uuid.v4();
dispatch({
type: 'LOAD_DEFAULT_RULE',
payload: {
queryID,
},
});
dispatch({
type: 'ADD_KAPACITOR_QUERY',
payload: {
@ -18,22 +42,17 @@ export function fetchRule() { // ruleID
};
}
export function loadDefaultRule() {
export function fetchRules(source) {
return (dispatch) => {
const queryID = uuid.v4();
const ruleID = uuid.v4();
getKapacitor(source).then((kapacitor) => {
getRules(kapacitor).then(({data: {rules}}) => {
dispatch({
type: 'LOAD_DEFAULT_RULE',
type: 'LOAD_RULES',
payload: {
queryID,
ruleID,
rules,
},
});
dispatch({
type: 'ADD_KAPACITOR_QUERY',
payload: {
queryId: queryID,
},
});
});
};
}

View File

@ -14,3 +14,18 @@ export function getRules(kapacitor) {
url: kapacitor.links.rules,
});
}
export function getRule(kapacitor, ruleID) {
return AJAX({
method: 'GET',
url: `${kapacitor.links.rules}/${ruleID}`,
});
}
export function editRule(rule) {
return AJAX({
method: 'PUT',
url: rule.links.self,
data: rule,
});
}

View File

@ -24,3 +24,5 @@ export const PERIODS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
export const CHANGES = ['change', '% change'];
export const SHIFTS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
export const ALERTS = ['hipchat', 'opsgenie', 'pagerduty', 'sensu', 'slack', 'smtp', 'talk', 'telegram', 'victorops'];
export const DEFAULT_RULE_ID = 'DEFAULT_RULE_ID';

View File

@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import {withRouter} from 'react-router';
import {connect} from 'react-redux';
import _ from 'lodash';
import DataSection from '../components/DataSection';
@ -12,8 +13,8 @@ import LineGraph from 'shared/components/LineGraph';
const RefreshingLineGraph = AutoRefresh(LineGraph);
import {getKapacitor, getKapacitorConfig} from 'shared/apis/index';
import Dropdown from 'shared/components/Dropdown';
import {ALERTS} from 'src/kapacitor/constants';
import {createRule} from 'src/kapacitor/apis';
import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants';
import {createRule, editRule} from 'src/kapacitor/apis';
export const KapacitorRulePage = React.createClass({
propTypes: {
@ -49,10 +50,15 @@ export const KapacitorRulePage = React.createClass({
};
},
isEditing() {
const {params} = this.props;
return params.ruleID && params.ruleID !== 'new';
},
componentDidMount() {
const {ruleID} = false; // this.props.params;
if (ruleID) {
this.props.kapacitorActions.fetchRule(ruleID);
const {ruleID} = this.props.params;
if (this.isEditing()) {
this.props.kapacitorActions.fetchRule(this.props.source, ruleID);
} else {
this.props.kapacitorActions.loadDefaultRule();
}
@ -64,25 +70,34 @@ export const KapacitorRulePage = React.createClass({
});
this.setState({kapacitor, enabledAlerts});
}).catch(() => {
this.props.addFlashMessage({type: 'failure', message: `There was a problem communicating with Kapacitor`});
this.props.addFlashMessage({type: 'failure', text: `There was a problem communicating with Kapacitor`});
}).catch(() => {
this.props.addFlashMessage({type: 'failure', message: `We couldn't find a configured Kapacitor for this source`});
this.props.addFlashMessage({type: 'failure', text: `We couldn't find a configured Kapacitor for this source`});
});
});
},
handleSave() {
const {queryConfigs, rules} = this.props;
const rule = rules[Object.keys(rules)[0]]; // this.props.params.taskID
const {queryConfigs, rules, params, source} = this.props;
if (this.isEditing()) { // If we are editing updated rule if not, create a new one
editRule(rules[params.ruleID]).then(() => {
this.props.addFlashMessage({type: 'success', text: `Rule successfully updated!`});
}).catch(() => {
this.props.addFlashMessage({type: 'failure', text: `There was a problem updating the rule`});
});
} else {
const rule = rules[DEFAULT_RULE_ID];
const newRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
});
delete newRule.queryID;
createRule(this.state.kapacitor, newRule).then(() => {
// maybe update the default rule in redux state.. and update the URL
this.props.router.push(`sources/${source.id}/alert-rules`);
this.props.addFlashMessage({type: 'success', text: `Rule successfully created`});
}).catch(() => {
this.props.addFlashMessage({type: 'failure', message: `There was a problem creating the rule`});
this.props.addFlashMessage({type: 'failure', text: `There was a problem creating the rule`});
});
}
},
handleChooseAlert(item) {
@ -132,13 +147,13 @@ export const KapacitorRulePage = React.createClass({
},
render() {
const {rules, queryConfigs, source} = this.props;
const rule = rules[Object.keys(rules)[0]]; // this.props.params.ruleID
const {rules, queryConfigs, source, params} = this.props;
const rule = this.isEditing() ? rules[params.ruleID] : rules[DEFAULT_RULE_ID];
const query = rule && queryConfigs[rule.queryID];
const autoRefreshMs = 30000;
if (!query) { // or somethin like that
return null; // or a spinner or somethin
if (!query) {
return <div className="page-spinner"></div>;
}
const queryText = selectStatement({lower: 'now() - 15m'}, query);
@ -218,7 +233,7 @@ export const KapacitorRulePage = React.createClass({
return (
<div className="kapacitor-rule-section">
<h3>Message</h3>
<textarea ref={(r) => this.message = r} onChange={() => this.handleMessageChange(rule)} />
<textarea ref={(r) => this.message = r} value={rule.message} onChange={() => this.handleMessageChange(rule)} />
</div>
);
},
@ -256,4 +271,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(KapacitorRulePage);
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(KapacitorRulePage));

View File

@ -1,39 +1,36 @@
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {Link} from 'react-router';
import {getRules} from 'src/kapacitor/apis';
import {getKapacitor} from 'src/shared/apis';
import * as kapacitorActionCreators from 'src/kapacitor/actions/view';
export const KapacitorRulesPage = React.createClass({
propTypes: {
source: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired, // 'influx-enterprise'
username: PropTypes.string.isRequired,
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
self: PropTypes.string.isRequired,
kapacitors: PropTypes.string.isRequired,
}).isRequired,
}),
}),
rules: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
trigger: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
alerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
})).isRequired,
actions: PropTypes.shape({
fetchRules: PropTypes.func.isRequired,
}).isRequired,
addFlashMessage: PropTypes.func,
},
getInitialState() {
return {
rules: [],
};
},
componentDidMount() {
getKapacitor(this.props.source).then((kapacitor) => {
getRules(kapacitor).then(({data: {rules}}) => {
this.setState({rules});
});
});
this.props.actions.fetchRules(this.props.source);
},
render() {
const {rules} = this.state;
const {source} = this.props;
const {source, rules} = this.props;
return (
<div className="kapacitor-rules-page">
@ -48,6 +45,7 @@ export const KapacitorRulesPage = React.createClass({
<div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">Alert Rules</h2>
<Link to={`/sources/${source.id}/alert-rules/new`} className="btn btn-sm btn-primary">Add New Rule</Link>
</div>
<div className="panel-body">
<table className="table v-center">
@ -82,4 +80,16 @@ export const KapacitorRulesPage = React.createClass({
},
});
export default KapacitorRulesPage;
function mapStateToProps(state) {
return {
rules: Object.values(state.rules),
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(kapacitorActionCreators, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(KapacitorRulesPage);

View File

@ -1,12 +1,13 @@
import {defaultRuleConfigs} from 'src/kapacitor/constants';
import {defaultRuleConfigs, DEFAULT_RULE_ID} from 'src/kapacitor/constants';
import _ from 'lodash';
export default function rules(state = {}, action) {
switch (action.type) {
case 'LOAD_DEFAULT_RULE': {
const {queryID, ruleID} = action.payload;
const {queryID} = action.payload;
return Object.assign({}, state, {
[ruleID]: {
id: ruleID,
[DEFAULT_RULE_ID]: {
id: DEFAULT_RULE_ID,
queryID,
trigger: 'threshold',
values: defaultRuleConfigs.threshold,
@ -18,10 +19,16 @@ export default function rules(state = {}, action) {
});
}
case 'LOAD_RULES': {
const theRules = action.payload.rules;
const ruleIDs = theRules.map(r => r.id);
return _.zipObject(ruleIDs, theRules);
}
case 'LOAD_RULE': {
const {ruleID, rule} = action.payload;
const {rule} = action.payload;
return Object.assign({}, state, {
[ruleID]: rule,
[rule.id]: rule,
});
}

View File

@ -8,6 +8,13 @@ export const ManageSources = React.createClass({
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}).isRequired,
source: PropTypes.shape({
id: PropTypes.string.isRequired,
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
self: PropTypes.string.isRequired,
}),
}),
},
getInitialState() {
return {
@ -59,7 +66,7 @@ export const ManageSources = React.createClass({
<div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">{sourcesTitle}</h2>
<Link to={`/sources/1/manage-sources/new`} className="btn btn-sm btn-primary">Add New Source</Link>
<Link to={`/sources/${this.props.source.id}/manage-sources/new`} className="btn btn-sm btn-primary">Add New Source</Link>
</div>
<div className="panel-body">
<div className="table-responsive margin-bottom-zero">