diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f8910bc29..957a5638e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards
1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path
1. [#1738](https://github.com/influxdata/chronograf/pull/1738): Add shared secret JWT authorization to InfluxDB
+1. [#1724](https://github.com/influxdata/chronograf/pull/1724): Add Pushover alert support
### UI Improvements
1. [#1707](https://github.com/influxdata/chronograf/pull/1707): Polish alerts table in status page to wrap text less
diff --git a/kapacitor/alerts.go b/kapacitor/alerts.go
index 759b161fe1..638b6fb8b8 100644
--- a/kapacitor/alerts.go
+++ b/kapacitor/alerts.go
@@ -21,7 +21,7 @@ func kapaHandler(handler string) (string, error) {
return "email", nil
case "http":
return "post", nil
- case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec", "log":
+ case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec", "log", "pushover":
return handler, nil
default:
return "", fmt.Errorf("Unsupported alert handler %s", handler)
diff --git a/kapacitor/ast.go b/kapacitor/ast.go
index bdff08eca4..82dc04a13c 100644
--- a/kapacitor/ast.go
+++ b/kapacitor/ast.go
@@ -412,7 +412,6 @@ func Reverse(script chronograf.TICKScript) (chronograf.AlertRule, error) {
rule.Query.RetentionPolicy = commonVars.RP
rule.Query.Measurement = commonVars.Measurement
rule.Query.GroupBy.Tags = commonVars.GroupBy
-
if commonVars.Filter.Operator == "==" {
rule.Query.AreTagsAccepted = true
}
@@ -492,6 +491,7 @@ func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) {
extractSlack(t, rule)
extractTalk(t, rule)
extractTelegram(t, rule)
+ extractPushover(t, rule)
extractTCP(t, rule)
extractLog(t, rule)
extractExec(t, rule)
@@ -501,7 +501,7 @@ func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) {
}
func extractHipchat(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.HipChatHandlers == nil {
+ if len(node.HipChatHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "hipchat")
@@ -527,7 +527,7 @@ func extractHipchat(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractOpsgenie(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.OpsGenieHandlers == nil {
+ if len(node.OpsGenieHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "opsgenie")
@@ -553,7 +553,7 @@ func extractOpsgenie(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractPagerduty(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.PagerDutyHandlers == nil {
+ if len(node.PagerDutyHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "pagerduty")
@@ -572,7 +572,7 @@ func extractPagerduty(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractVictorops(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.VictorOpsHandlers == nil {
+ if len(node.VictorOpsHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "victorops")
@@ -591,7 +591,7 @@ func extractVictorops(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractEmail(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.EmailHandlers == nil {
+ if len(node.EmailHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "smtp")
@@ -607,7 +607,7 @@ func extractEmail(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractPost(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.HTTPPostHandlers == nil {
+ if len(node.HTTPPostHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "http")
@@ -640,7 +640,7 @@ func extractPost(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractAlerta(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.AlertaHandlers == nil {
+ if len(node.AlertaHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "alerta")
@@ -709,7 +709,7 @@ func extractAlerta(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractSensu(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.SensuHandlers == nil {
+ if len(node.SensuHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "sensu")
@@ -721,7 +721,7 @@ func extractSensu(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractSlack(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.SlackHandlers == nil {
+ if len(node.SlackHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "slack")
@@ -753,7 +753,7 @@ func extractSlack(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
rule.AlertNodes = append(rule.AlertNodes, alert)
}
func extractTalk(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.TalkHandlers == nil {
+ if len(node.TalkHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "talk")
@@ -764,7 +764,7 @@ func extractTalk(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
rule.AlertNodes = append(rule.AlertNodes, alert)
}
func extractTelegram(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.TelegramHandlers == nil {
+ if len(node.TelegramHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "telegram")
@@ -802,7 +802,7 @@ func extractTelegram(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractTCP(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.TcpHandlers == nil {
+ if len(node.TcpHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "tcp")
@@ -819,7 +819,7 @@ func extractTCP(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractLog(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.LogHandlers == nil {
+ if len(node.LogHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "log")
@@ -836,7 +836,7 @@ func extractLog(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
}
func extractExec(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
- if node.ExecHandlers == nil {
+ if len(node.ExecHandlers) == 0 {
return
}
rule.Alerts = append(rule.Alerts, "exec")
@@ -851,3 +851,51 @@ func extractExec(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
rule.AlertNodes = append(rule.AlertNodes, alert)
}
+
+func extractPushover(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
+ if len(node.PushoverHandlers) == 0 {
+ return
+ }
+ rule.Alerts = append(rule.Alerts, "pushover")
+ a := node.PushoverHandlers[0]
+ alert := chronograf.KapacitorNode{
+ Name: "pushover",
+ }
+
+ if a.Device != "" {
+ alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
+ Name: "device",
+ Args: []string{a.Device},
+ })
+ }
+
+ if a.Title != "" {
+ alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
+ Name: "title",
+ Args: []string{a.Title},
+ })
+ }
+
+ if a.URL != "" {
+ alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
+ Name: "URL",
+ Args: []string{a.URL},
+ })
+ }
+
+ if a.URLTitle != "" {
+ alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
+ Name: "URLTitle",
+ Args: []string{a.URLTitle},
+ })
+ }
+
+ if a.Sound != "" {
+ alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
+ Name: "sound",
+ Args: []string{a.Sound},
+ })
+ }
+
+ rule.AlertNodes = append(rule.AlertNodes, alert)
+}
diff --git a/server/swagger.json b/server/swagger.json
index ddd452ea7a..058be7dadf 100644
--- a/server/swagger.json
+++ b/server/swagger.json
@@ -2714,6 +2714,7 @@
"hipchat",
"opsgenie",
"pagerduty",
+ "pushover",
"victorops",
"smtp",
"email",
diff --git a/ui/spec/kapacitor/reducers/rulesSpec.js b/ui/spec/kapacitor/reducers/rulesSpec.js
index 3f981aca81..87223b9b83 100644
--- a/ui/spec/kapacitor/reducers/rulesSpec.js
+++ b/ui/spec/kapacitor/reducers/rulesSpec.js
@@ -11,6 +11,7 @@ import {
updateMessage,
updateAlerts,
updateAlertNodes,
+ updateAlertProperty,
updateRuleName,
deleteRuleSuccess,
updateRuleStatusSuccess,
@@ -200,6 +201,106 @@ describe('Kapacitor.Reducers.rules', () => {
expect(newState[ruleID].details).to.equal(details)
})
+ it('can update properties', () => {
+ const ruleID = 1
+
+ const alertNodeName = 'pushover'
+
+ const alertProperty1_Name = 'device'
+ const alertProperty1_ArgsOrig =
+ 'pineapple_kingdom_control_room,bob_cOreos_watch'
+ const alertProperty1_ArgsDiff = 'pineapple_kingdom_control_tower'
+
+ const alertProperty2_Name = 'URLTitle'
+ const alertProperty2_ArgsOrig = 'Cubeapple Rising'
+ const alertProperty2_ArgsDiff = 'Cubeapple Falling'
+
+ const alertProperty1_Orig = {
+ name: alertProperty1_Name,
+ args: [alertProperty1_ArgsOrig],
+ }
+ const alertProperty1_Diff = {
+ name: alertProperty1_Name,
+ args: [alertProperty1_ArgsDiff],
+ }
+ const alertProperty2_Orig = {
+ name: alertProperty2_Name,
+ args: [alertProperty2_ArgsOrig],
+ }
+ const alertProperty2_Diff = {
+ name: alertProperty2_Name,
+ args: [alertProperty2_ArgsDiff],
+ }
+
+ const initialState = {
+ [ruleID]: {
+ id: ruleID,
+ alertNodes: [
+ {
+ name: 'pushover',
+ args: null,
+ properties: null,
+ },
+ ],
+ },
+ }
+
+ const getAlertPropertyArgs = (matchState, propertyName) =>
+ matchState[ruleID].alertNodes
+ .find(node => node.name === alertNodeName)
+ .properties.find(property => property.name === propertyName).args[0]
+
+ // add first property
+ let newState = reducer(
+ initialState,
+ updateAlertProperty(ruleID, alertNodeName, alertProperty1_Orig)
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
+ alertProperty1_ArgsOrig
+ )
+
+ // change first property
+ newState = reducer(
+ initialState,
+ updateAlertProperty(ruleID, alertNodeName, alertProperty1_Diff)
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
+ alertProperty1_ArgsDiff
+ )
+
+ // add second property
+ newState = reducer(
+ initialState,
+ updateAlertProperty(ruleID, alertNodeName, alertProperty2_Orig)
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
+ alertProperty1_ArgsDiff
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty2_Name)).to.equal(
+ alertProperty2_ArgsOrig
+ )
+ expect(
+ newState[ruleID].alertNodes.find(node => node.name === alertNodeName)
+ .properties.length
+ ).to.equal(2)
+
+ // change second property
+ newState = reducer(
+ initialState,
+ updateAlertProperty(ruleID, alertNodeName, alertProperty2_Diff)
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
+ alertProperty1_ArgsDiff
+ )
+ expect(getAlertPropertyArgs(newState, alertProperty2_Name)).to.equal(
+ alertProperty2_ArgsDiff
+ )
+ expect(
+ newState[ruleID].alertNodes.find(node => node.name === alertNodeName)
+ .properties.length
+ ).to.equal(2)
+ })
+
it('can update status', () => {
const ruleID = 1
const status = 'enabled'
diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js
index 1452c32945..06d16500f1 100644
--- a/ui/src/kapacitor/actions/view/index.js
+++ b/ui/src/kapacitor/actions/view/index.js
@@ -114,6 +114,15 @@ export function updateDetails(ruleID, details) {
}
}
+export const updateAlertProperty = (ruleID, alertNodeName, alertProperty) => ({
+ type: 'UPDATE_RULE_ALERT_PROPERTY',
+ payload: {
+ ruleID,
+ alertNodeName,
+ alertProperty,
+ },
+})
+
export function updateAlerts(ruleID, alerts) {
return {
type: 'UPDATE_RULE_ALERTS',
@@ -124,12 +133,12 @@ export function updateAlerts(ruleID, alerts) {
}
}
-export function updateAlertNodes(ruleID, alertType, alertNodesText) {
+export function updateAlertNodes(ruleID, alertNodeName, alertNodesText) {
return {
type: 'UPDATE_RULE_ALERT_NODES',
payload: {
ruleID,
- alertType,
+ alertNodeName,
alertNodesText,
},
}
diff --git a/ui/src/kapacitor/components/AlertTabs.js b/ui/src/kapacitor/components/AlertTabs.js
index 51a0a81c17..afe2d7f537 100644
--- a/ui/src/kapacitor/components/AlertTabs.js
+++ b/ui/src/kapacitor/components/AlertTabs.js
@@ -13,6 +13,7 @@ import {
HipChatConfig,
OpsGenieConfig,
PagerDutyConfig,
+ PushoverConfig,
SensuConfig,
SlackConfig,
SMTPConfig,
@@ -124,99 +125,97 @@ class AlertTabs extends Component {
this.handleTest('slack', properties)
}
- const tabs = [
- {
+ const supportedConfigs = {
+ alerta: {
type: 'Alerta',
- component: (
+ renderComponent: () =>
Send this Alert to:
- {DEFAULT_ALERT_LABELS[alert]}
-
+ {args.label}
+ Optional Alert Parameters Templates:
+ {template.label}
+
+
+const {func, shape, string} = PropTypes
+
+CodeData.propTypes = {
+ onClickTemplate: func,
+ template: shape({
+ label: string,
+ text: string,
+ }),
+}
+
+export default CodeData
diff --git a/ui/src/kapacitor/components/RuleMessage.js b/ui/src/kapacitor/components/RuleMessage.js
index 7e333cb43e..5112397d7e 100644
--- a/ui/src/kapacitor/components/RuleMessage.js
+++ b/ui/src/kapacitor/components/RuleMessage.js
@@ -1,41 +1,35 @@
-import React, {PropTypes} from 'react'
+import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
-import ReactTooltip from 'react-tooltip'
-import RuleMessageAlertConfig from 'src/kapacitor/components/RuleMessageAlertConfig'
+import RuleMessageOptions from 'src/kapacitor/components/RuleMessageOptions'
+import RuleMessageText from 'src/kapacitor/components/RuleMessageText'
+import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates'
-import {RULE_MESSAGE_TEMPLATES as templates, DEFAULT_ALERTS} from '../constants'
+import {DEFAULT_ALERTS, RULE_ALERT_OPTIONS} from 'src/kapacitor/constants'
-const {arrayOf, func, shape, string} = PropTypes
+class RuleMessage extends Component {
+ constructor(props) {
+ super(props)
-export const RuleMessage = React.createClass({
- propTypes: {
- rule: shape({}).isRequired,
- actions: shape({
- updateMessage: func.isRequired,
- updateDetails: func.isRequired,
- }).isRequired,
- enabledAlerts: arrayOf(string.isRequired).isRequired,
- },
-
- getInitialState() {
- return {
- selectedAlert: null,
- selectedAlertProperty: null,
+ this.state = {
+ selectedAlertNodeName: null,
}
- },
+
+ this.handleChangeMessage = ::this.handleChangeMessage
+ this.handleChooseAlert = ::this.handleChooseAlert
+ }
handleChangeMessage() {
const {actions, rule} = this.props
actions.updateMessage(rule.id, this.message.value)
- },
+ }
handleChooseAlert(item) {
const {actions} = this.props
actions.updateAlerts(item.ruleID, [item.text])
actions.updateAlertNodes(item.ruleID, item.text, '')
- this.setState({selectedAlert: item.text})
- },
+ this.setState({selectedAlertNodeName: item.text})
+ }
render() {
const {rule, actions, enabledAlerts} = this.props
@@ -43,13 +37,14 @@ export const RuleMessage = React.createClass({
return {text, ruleID: rule.id}
})
- const alerts = enabledAlerts
- .map(text => {
+ const alerts = [
+ ...defaultAlertEndpoints,
+ ...enabledAlerts.map(text => {
return {text, ruleID: rule.id}
- })
- .concat(defaultAlertEndpoints)
+ }),
+ ]
- const selectedAlert = rule.alerts[0] || alerts[0].text
+ const selectedAlertNodeName = rule.alerts[0] || alerts[0].text
return (
- {alerts.map(alert =>
-
- {label}
-
- )
- },
-})
+RuleMessage.propTypes = {
+ rule: shape({}).isRequired,
+ actions: shape({
+ updateAlertNodes: func.isRequired,
+ updateMessage: func.isRequired,
+ updateDetails: func.isRequired,
+ updateAlertProperty: func.isRequired,
+ }).isRequired,
+ enabledAlerts: arrayOf(string.isRequired).isRequired,
+}
export default RuleMessage
diff --git a/ui/src/kapacitor/components/RuleMessageAlertConfig.js b/ui/src/kapacitor/components/RuleMessageAlertConfig.js
deleted file mode 100644
index 0870e11b86..0000000000
--- a/ui/src/kapacitor/components/RuleMessageAlertConfig.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, {PropTypes} from 'react'
-
-import {
- DEFAULT_ALERT_PLACEHOLDERS,
- DEFAULT_ALERT_LABELS,
- ALERT_NODES_ACCESSORS,
-} from '../constants'
-
-const RuleMessageAlertConfig = ({updateAlertNodes, alert, rule}) => {
- if (!Object.keys(DEFAULT_ALERT_PLACEHOLDERS).find(a => a === alert)) {
- return null
- }
- if (!Object.keys(DEFAULT_ALERT_LABELS).find(a => a === alert)) {
- return null
- }
- return (
-
Need help setting up Pushover?
Check out the docs here.
Need help finding your chat id?
Check out these steps.
Need help finding your chat id?
Chec
const telegramTokenLink =
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-api-access-token'
export const TELEGRAM_TOKEN_TIP = `
Need help finding your token?
Check out these steps.
Need help creating a token?
Check out these steps.