From 658d39dbc3a5f51e9a666fd93f1aeab29ebff7d0 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Nov 2016 15:25:51 -0700 Subject: [PATCH] Add alerts to kapacitor rule --- ui/spec/kapacitor/reducers/rulesSpec.js | 16 ++++++ ui/src/kapacitor/actions/view/index.js | 10 ++++ ui/src/kapacitor/components/ValuesSection.js | 4 +- .../kapacitor/containers/KapacitorRulePage.js | 50 ++++++++++++++----- ui/src/kapacitor/reducers/rules.js | 12 +++++ 5 files changed, 78 insertions(+), 14 deletions(-) diff --git a/ui/spec/kapacitor/reducers/rulesSpec.js b/ui/spec/kapacitor/reducers/rulesSpec.js index ab25277fd..e474d0717 100644 --- a/ui/spec/kapacitor/reducers/rulesSpec.js +++ b/ui/spec/kapacitor/reducers/rulesSpec.js @@ -5,6 +5,7 @@ import { chooseTrigger, updateRuleValues, updateMessage, + updateAlerts, } from 'src/kapacitor/actions/view'; describe('Kapacitor.Reducers.rules', () => { @@ -66,4 +67,19 @@ describe('Kapacitor.Reducers.rules', () => { const newState = reducer(initialState, updateMessage(ruleID, message)); expect(newState[ruleID].message).to.equal(message); }); + + it('can update the alerts', () => { + const ruleID = 1; + const initialState = { + [ruleID]: { + id: ruleID, + queryID: 988, + alerts: [], + } + }; + + const alerts = ['slack']; + const newState = reducer(initialState, updateAlerts(ruleID, alerts)); + expect(newState[ruleID].alerts).to.equal(alerts); + }); }); diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index ef3519d86..fc62d4652 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -68,3 +68,13 @@ export function updateMessage(ruleID, message) { }, }; } + +export function updateAlerts(ruleID, alerts) { + return { + type: 'UPDATE_RULE_ALERTS', + payload: { + ruleID, + alerts, + }, + }; +} diff --git a/ui/src/kapacitor/components/ValuesSection.js b/ui/src/kapacitor/components/ValuesSection.js index 054e6786b..86262c190 100644 --- a/ui/src/kapacitor/components/ValuesSection.js +++ b/ui/src/kapacitor/components/ValuesSection.js @@ -92,7 +92,7 @@ const Threshold = React.createClass({ }); } - const operators = mapToItems(['greater than', 'less than', 'equal to', 'not equal to'], 'operator'); + const operators = mapToItems(['greater than', 'equal to or greater', 'equal to or less than', 'less than', 'equal to', 'not equal to'], 'operator'); const relations = mapToItems(['once', 'more than ', 'less than'], 'relation'); const periods = mapToItems(['1m', '5m', '10m', '30m', '1h', '2h', '1h'], 'period'); @@ -144,7 +144,7 @@ const Relative = React.createClass({ const changes = mapToItems(['change', '% change'], 'change'); const periods = mapToItems(['1m', '5m', '10m', '30m', '1h', '2h', '1h'], 'period'); const shifts = mapToItems(['1m', '5m', '10m', '30m', '1h', '2h', '1h'], 'shift'); - const operators = mapToItems(['greater than', 'less than', 'equal to', 'not equal to'], 'operator'); + const operators = mapToItems(['greater than', 'equal to or greater', 'equal to or less than', 'less than', 'equal to', 'not equal to'], 'operator'); return (
diff --git a/ui/src/kapacitor/containers/KapacitorRulePage.js b/ui/src/kapacitor/containers/KapacitorRulePage.js index 0a9975aec..9ef62132d 100644 --- a/ui/src/kapacitor/containers/KapacitorRulePage.js +++ b/ui/src/kapacitor/containers/KapacitorRulePage.js @@ -1,5 +1,6 @@ import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; +import _ from 'lodash'; import DataSection from '../components/DataSection'; import ValuesSection from '../components/ValuesSection'; import * as kapacitorActionCreators from '../actions/view'; @@ -9,6 +10,8 @@ import selectStatement from 'src/chronograf/utils/influxql/select'; import AutoRefresh from 'shared/components/AutoRefresh'; import LineGraph from 'shared/components/LineGraph'; const RefreshingLineGraph = AutoRefresh(LineGraph); +import {getKapacitor, getKapacitorConfig} from 'shared/apis/index'; +import Dropdown from 'shared/components/Dropdown'; export const KapacitorRulePage = React.createClass({ propTypes: { @@ -27,6 +30,7 @@ export const KapacitorRulePage = React.createClass({ chooseTrigger: PropTypes.func.isRequired, updateRuleValues: PropTypes.func.isRequired, updateMessage: PropTypes.func.isRequired, + updateAlerts: PropTypes.func.isRequired, }).isRequired, queryActions: PropTypes.shape({}).isRequired, params: PropTypes.shape({ @@ -34,6 +38,12 @@ export const KapacitorRulePage = React.createClass({ }).isRequired, }, + getInitialState() { + return { + enabledAlerts: [], + }; + }, + componentDidMount() { const {ruleID} = this.props.params; if (ruleID) { @@ -41,46 +51,61 @@ export const KapacitorRulePage = React.createClass({ } else { this.props.kapacitorActions.loadDefaultRule(); } + + getKapacitor(this.props.source).then((kapacitor) => { + getKapacitorConfig(kapacitor).then(({data: {sections}}) => { + const enabledAlerts = Object.keys(sections).filter((section) => { + return _.get(sections, [section, 'elements', '0', 'options', 'enabled'], false); + }); + this.setState({enabledAlerts}); + }).catch(() => { + this.props.addFlashMessage({type: 'failure', message: `There was a problem communicating with Kapacitor`}); + }).catch(() => { + this.props.addFlashMessage({type: 'failure', message: `We couldn't find a configured Kapacitor for this source`}); + }); + }); }, handleSave() { console.log(this.props.rules); // eslint-disable-line no-console }, + handleChooseAlert(item) { + this.props.kapacitorActions.updateAlerts(item.ruleID, [item.text]); + }, + createUnderlayCallback(rule) { return (canvas, area, dygraph) => { if (rule.trigger !== 'threshold') { return; } + const theOnePercent = 0.01; let highlightStart = 0; let highlightEnd = 0; switch (rule.values.operator) { + case 'equal to or greater': case 'greater than': { highlightStart = rule.values.value; highlightEnd = dygraph.yAxisRange()[1]; break; } + case 'equal to or less than': case 'less than': { highlightStart = dygraph.yAxisRange()[0]; highlightEnd = rule.values.value; break; } + case 'not equal to': case 'equal to': { const width = (theOnePercent) * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0]); highlightStart = +rule.values.value - width; highlightEnd = +rule.values.value + width; break; } - case 'not equal to': { - const width = (theOnePercent) * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0]); - highlightStart = +rule.values.value - width; - highlightEnd = +rule.values.value + width; - break; - } } const bottom = dygraph.toDomYCoord(highlightStart); @@ -147,7 +172,7 @@ export const KapacitorRulePage = React.createClass({
- {this.renderAlertsSection()} + {this.renderAlertsSection(rule)}
@@ -183,15 +208,16 @@ export const KapacitorRulePage = React.createClass({ ); }, - renderAlertsSection() { - // hit kapacitor config endpoint and filter sections by the "enabled" property - const alertOptions = ['Slack', 'VictorOps'].map((destination) => { - return ; + renderAlertsSection(rule) { + const alerts = this.state.enabledAlerts.map((text) => { + return {text, ruleID: rule.id}; }); + return (

Alerts

-

The Alert should

+ The Alert should +
); }, diff --git a/ui/src/kapacitor/reducers/rules.js b/ui/src/kapacitor/reducers/rules.js index a7b6e2f49..f153cc2a5 100644 --- a/ui/src/kapacitor/reducers/rules.js +++ b/ui/src/kapacitor/reducers/rules.js @@ -11,6 +11,9 @@ export default function rules(state = {}, action) { trigger: 'threshold', values: defaultRuleConfigs.threshold, message: '', + alerts: [], + every: '30s', + name: 'Random album title', }, }); } @@ -51,6 +54,15 @@ export default function rules(state = {}, action) { }), }); } + + case 'UPDATE_RULE_ALERTS': { + const {ruleID, alerts} = action.payload; + return Object.assign({}, state, { + [ruleID]: Object.assign({}, state[ruleID], { + alerts, + }), + }); + } } return state; }