Convert KapacitorRule tree to typescript

pull/10616/head
Iris Scholten 2018-06-15 16:40:34 -07:00
parent 93e72efb08
commit ae1c4484be
21 changed files with 614 additions and 490 deletions

View File

@ -1,24 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const CodeData = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
const {func, shape, string} = PropTypes
CodeData.propTypes = {
onClickTemplate: func,
template: shape({
label: string,
text: string,
}),
}
export default CodeData

View File

@ -0,0 +1,20 @@
import React, {SFC} from 'react'
import {RuleMessage} from 'src/types/kapacitor'
interface Props {
onClickTemplate: () => void
template: RuleMessage
}
const CodeData: SFC<Props> = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
export default CodeData

View File

@ -24,7 +24,7 @@ interface Props {
query: QueryConfig query: QueryConfig
isDeadman: boolean isDeadman: boolean
isKapacitorRule: boolean isKapacitorRule: boolean
onAddEvery: () => void onAddEvery: (every?: string) => void
timeRange: TimeRange timeRange: TimeRange
} }

View File

@ -1,13 +1,24 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
import {PERIODS} from 'src/kapacitor/constants' import {PERIODS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown' import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const periods = PERIODS.map(text => { const periods = PERIODS.map(text => {
return {text} return {text}
}) })
const Deadman = ({rule, onChange}) => ( interface Item {
text: string
}
interface Props {
rule: AlertRule
onChange: (item: Item) => void
}
const Deadman: SFC<Props> = ({rule, onChange}) => (
<div className="rule-section--row rule-section--row-first rule-section--row-last"> <div className="rule-section--row rule-section--row-first rule-section--row-last">
<p>Send Alert if Data is missing for</p> <p>Send Alert if Data is missing for</p>
<Dropdown <Dropdown
@ -20,15 +31,4 @@ const Deadman = ({rule, onChange}) => (
</div> </div>
) )
const {shape, string, func} = PropTypes
Deadman.propTypes = {
rule: shape({
values: shape({
period: string,
}),
}),
onChange: func.isRequired,
}
export default Deadman export default Deadman

View File

@ -1,20 +1,20 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {InjectedRouter} from 'react-router'
import NameSection from 'src/kapacitor/components/NameSection' import NameSection from 'src/kapacitor/components/NameSection'
import ValuesSection from 'src/kapacitor/components/ValuesSection' import ValuesSection from 'src/kapacitor/components/ValuesSection'
import RuleHeader from 'src/kapacitor/components/RuleHeader' import RuleHeader from 'src/kapacitor/components/RuleHeader'
import RuleHandlers from 'src/kapacitor/components/RuleHandlers' import RuleHandlers from 'src/kapacitor/components/RuleHandlers'
import RuleMessage from 'src/kapacitor/components/RuleMessage' import RuleMessage from 'src/kapacitor/components/RuleMessage'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {createRule, editRule} from 'src/kapacitor/apis' import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql' import buildInfluxQLQuery from 'src/utils/influxql'
import {timeRanges} from 'shared/data/timeRanges' import {timeRanges} from 'src/shared/data/timeRanges'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {notify as notifyAction} from 'shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import { import {
notifyAlertRuleCreated, notifyAlertRuleCreated,
@ -24,11 +24,52 @@ import {
notifyAlertRuleRequiresQuery, notifyAlertRuleRequiresQuery,
notifyAlertRuleRequiresConditionValue, notifyAlertRuleRequiresConditionValue,
notifyAlertRuleDeadmanInvalid, notifyAlertRuleDeadmanInvalid,
} from 'shared/copy/notifications' } from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
AlertRule,
Notification,
Kapacitor,
QueryConfig,
TimeRange,
} from 'src/types'
import {Handler} from 'src/types/kapacitor'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Props {
source: Source
rule: AlertRule
query: QueryConfig
queryConfigs: QueryConfig[]
queryConfigActions: KapacitorQueryConfigActions
ruleActions: KapacitorRuleActions
notify: (message: Notification) => void
ruleID: string
handlersFromConfig: Handler[]
router: InjectedRouter
kapacitor: Kapacitor
configLink: string
}
interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface State {
timeRange: TimeRange
}
@ErrorHandling @ErrorHandling
class KapacitorRule extends Component { class KapacitorRule extends Component<Props, State> {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
@ -36,137 +77,7 @@ class KapacitorRule extends Component {
} }
} }
handleChooseTimeRange = ({lower}) => { public render() {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
handleCreate = pathname => {
const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
const newRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
delete newRule.queryID
createRule(kapacitor, newRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleCreated(newRule.name))
})
.catch(e => {
notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
})
}
handleEdit = pathname => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
handleSaveToConfig = configName => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError()) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
handleAddEvery = frequency => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
validationError = () => {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
handleRuleTypeDropdownChange = ({type, text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
handleRuleTypeInputChange = e => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
handleDeadmanChange = ({text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
render() {
const { const {
rule, rule,
source, source,
@ -183,7 +94,7 @@ class KapacitorRule extends Component {
<RuleHeader <RuleHeader
source={source} source={source}
onSave={this.handleSave} onSave={this.handleSave}
validationError={this.validationError()} validationError={this.validationError}
/> />
<FancyScrollbar className="page-contents fancy-scroll--kapacitor"> <FancyScrollbar className="page-contents fancy-scroll--kapacitor">
<div className="container-fluid"> <div className="container-fluid">
@ -215,7 +126,7 @@ class KapacitorRule extends Component {
ruleActions={ruleActions} ruleActions={ruleActions}
handlersFromConfig={handlersFromConfig} handlersFromConfig={handlersFromConfig}
onGoToConfig={this.handleSaveToConfig} onGoToConfig={this.handleSaveToConfig}
validationError={this.validationError()} validationError={this.validationError}
/> />
<RuleMessage rule={rule} ruleActions={ruleActions} /> <RuleMessage rule={rule} ruleActions={ruleActions} />
</div> </div>
@ -226,27 +137,138 @@ class KapacitorRule extends Component {
</div> </div>
) )
} }
}
const {arrayOf, func, shape, string} = PropTypes private handleChooseTimeRange = ({lower}: TimeRange) => {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
KapacitorRule.propTypes = { private handleCreate = (pathname?: string) => {
source: shape({}).isRequired, const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
rule: shape({
values: shape({}), const newRule = Object.assign({}, rule, {
}).isRequired, query: queryConfigs[rule.queryID],
query: shape({}).isRequired, })
queryConfigs: shape({}).isRequired, delete newRule.queryID
queryConfigActions: shape({}).isRequired,
ruleActions: shape({}).isRequired, createRule(kapacitor, newRule)
notify: func.isRequired, .then(() => {
ruleID: string.isRequired, router.push(pathname || `/sources/${source.id}/alert-rules`)
handlersFromConfig: arrayOf(shape({})).isRequired, notify(notifyAlertRuleCreated(newRule.name))
router: shape({ })
push: func.isRequired, .catch(e => {
}).isRequired, notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
kapacitor: shape({}).isRequired, })
configLink: string.isRequired, }
private handleEdit = (pathname?: string) => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
private handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
private handleSaveToConfig = (configName: string) => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
private handleAddEvery = (frequency: string) => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
private handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
private get validationError(): string {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({lower: ''}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
private deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
private handleRuleTypeDropdownChange = ({type, text}: TypeItem) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
private handleRuleTypeInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
private handleDeadmanChange = ({text}: Item) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
} }
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -1,11 +1,25 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent, KeyboardEvent} from 'react'
import PropTypes from 'prop-types'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
interface Props {
defaultName: string
onRuleRename: (id: string, name: string) => void
rule: AlertRule
}
interface State {
reset: boolean
}
@ErrorHandling @ErrorHandling
class NameSection extends Component { class NameSection extends Component<Props, State> {
constructor(props) { private inputRef: HTMLInputElement
constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {
@ -13,14 +27,22 @@ class NameSection extends Component {
} }
} }
handleInputBlur = reset => e => { public handleInputBlur = (reset: boolean) => (
e: ChangeEvent<HTMLInputElement>
): void => {
const {defaultName, onRuleRename, rule} = this.props const {defaultName, onRuleRename, rule} = this.props
onRuleRename(rule.id, reset ? defaultName : e.target.value) let ruleName: string
if (reset) {
ruleName = defaultName
} else {
ruleName = e.target.value
}
onRuleRename(rule.id, ruleName)
this.setState({reset: false}) this.setState({reset: false})
} }
handleKeyDown = e => { public handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.inputRef.blur() this.inputRef.blur()
} }
@ -30,15 +52,13 @@ class NameSection extends Component {
} }
} }
render() { public render() {
const {rule, defaultName} = this.props const {defaultName} = this.props
const {reset} = this.state const {reset} = this.state
return ( return (
<div className="rule-section"> <div className="rule-section">
<h3 className="rule-section--heading"> <h3 className="rule-section--heading">{this.header}</h3>
{rule.id === DEFAULT_RULE_ID ? 'Name this Alert Rule' : 'Name'}
</h3>
<div className="rule-section--body"> <div className="rule-section--body">
<div className="rule-section--row rule-section--row-first rule-section--row-last"> <div className="rule-section--row rule-section--row-first rule-section--row-last">
<input <input
@ -55,14 +75,18 @@ class NameSection extends Component {
</div> </div>
) )
} }
}
const {func, string, shape} = PropTypes private get header() {
const {
rule: {id},
} = this.props
NameSection.propTypes = { if (id === DEFAULT_RULE_ID) {
defaultName: string.isRequired, return 'Name this Alert Rule'
onRuleRename: func.isRequired, }
rule: shape({}).isRequired,
return 'Name'
}
} }
export default NameSection export default NameSection

View File

@ -1,14 +1,27 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
const mapToItems = (arr, type) => arr.map(text => ({text, type})) import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const mapToItems = (arr: string[], type: string) =>
arr.map(text => ({text, type}))
const changes = mapToItems(CHANGES, 'change') const changes = mapToItems(CHANGES, 'change')
const shifts = mapToItems(SHIFTS, 'shift') const shifts = mapToItems(SHIFTS, 'shift')
const operators = mapToItems(RELATIVE_OPERATORS, 'operator') const operators = mapToItems(RELATIVE_OPERATORS, 'operator')
const Relative = ({ interface TypeItem {
type: string
text: string
}
interface Props {
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onDropdownChange: (item: TypeItem) => void
rule: AlertRule
}
const Relative: SFC<Props> = ({
onRuleTypeInputChange, onRuleTypeInputChange,
onDropdownChange, onDropdownChange,
rule: { rule: {
@ -46,7 +59,7 @@ const Relative = ({
style={{width: '160px', marginLeft: '6px'}} style={{width: '160px', marginLeft: '6px'}}
type="text" type="text"
name="lower" name="lower"
spellCheck="false" spellCheck={false}
value={value} value={value}
onChange={onRuleTypeInputChange} onChange={onRuleTypeInputChange}
required={true} required={true}
@ -56,19 +69,4 @@ const Relative = ({
</div> </div>
) )
const {shape, string, func} = PropTypes
Relative.propTypes = {
onRuleTypeInputChange: func.isRequired,
onDropdownChange: func.isRequired,
rule: shape({
values: shape({
change: string,
shift: string,
operator: string,
value: string,
}),
}),
}
export default Relative export default Relative

View File

@ -26,7 +26,7 @@ interface Props {
rule: AlertRule rule: AlertRule
ruleActions: RuleActions ruleActions: RuleActions
handlersFromConfig: Handler[] handlersFromConfig: Handler[]
onGoToConfig: () => void onGoToConfig: (configName: string) => void
validationError: string validationError: string
} }

View File

@ -1,15 +1,24 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave' import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {Source} from 'src/types'
interface Props {
source: Source
onSave: () => void
validationError: string
}
@ErrorHandling @ErrorHandling
class RuleHeader extends Component { class RuleHeader extends Component<Props> {
constructor(props) { constructor(props: Props) {
super(props) super(props)
} }
render() { public render() {
const {source, onSave, validationError} = this.props const {source, onSave, validationError} = this.props
return ( return (
@ -29,12 +38,4 @@ class RuleHeader extends Component {
} }
} }
const {func, shape, string} = PropTypes
RuleHeader.propTypes = {
source: shape({}).isRequired,
onSave: func.isRequired,
validationError: string.isRequired,
}
export default RuleHeader export default RuleHeader

View File

@ -1,39 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactTooltip from 'react-tooltip'
import SourceIndicator from 'shared/components/SourceIndicator'
const RuleHeaderSave = ({onSave, validationError}) => (
<div className="page-header__right">
<SourceIndicator />
{validationError ? (
<button
className="btn btn-success btn-sm disabled"
data-for="save-kapacitor-tooltip"
data-tip={validationError}
>
Save Rule
</button>
) : (
<button className="btn btn-success btn-sm" onClick={onSave}>
Save Rule
</button>
)}
<ReactTooltip
id="save-kapacitor-tooltip"
effect="solid"
html={true}
place="bottom"
class="influx-tooltip kapacitor-tooltip"
/>
</div>
)
const {func, string} = PropTypes
RuleHeaderSave.propTypes = {
onSave: func.isRequired,
validationError: string.isRequired,
}
export default RuleHeaderSave

View File

@ -0,0 +1,57 @@
import React, {Component} from 'react'
import ReactTooltip from 'react-tooltip'
import SourceIndicator from 'src/shared/components/SourceIndicator'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Source} from 'src/types'
interface Props {
onSave: () => void
validationError: string
source: Source
}
@ErrorHandling
class RuleHeaderSave extends Component<Props> {
public render() {
const {source} = this.props
return (
<div className="page-header__right">
<SourceIndicator sourceOverride={source} />
{this.saveRuleButton}
<ReactTooltip
id="save-kapacitor-tooltip"
effect="solid"
html={true}
place="bottom"
class="influx-tooltip kapacitor-tooltip"
/>
</div>
)
}
private get saveRuleButton() {
const {onSave, validationError} = this.props
if (validationError) {
return (
<button
className="btn btn-success btn-sm disabled"
data-for="save-kapacitor-tooltip"
data-tip={validationError}
>
Save Rule
</button>
)
} else {
return (
<button className="btn btn-success btn-sm" onClick={onSave}>
Save Rule
</button>
)
}
}
}
export default RuleHeaderSave

View File

@ -1,22 +1,24 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import RuleMessageText from 'src/kapacitor/components/RuleMessageText' import RuleMessageText from 'src/kapacitor/components/RuleMessageText'
import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates' import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
import {KapacitorRuleActions} from 'src/types/actions'
interface Props {
rule: AlertRule
ruleActions: KapacitorRuleActions
}
@ErrorHandling @ErrorHandling
class RuleMessage extends Component { class RuleMessage extends Component<Props> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
handleChangeMessage = e => { public render() {
const {ruleActions, rule} = this.props
ruleActions.updateMessage(rule.id, e.target.value)
}
render() {
const {rule, ruleActions} = this.props const {rule, ruleActions} = this.props
return ( return (
@ -35,15 +37,11 @@ class RuleMessage extends Component {
</div> </div>
) )
} }
}
const {func, shape} = PropTypes private handleChangeMessage = (e: ChangeEvent<HTMLTextAreaElement>) => {
const {ruleActions, rule} = this.props
RuleMessage.propTypes = { ruleActions.updateMessage(rule.id, e.target.value)
rule: shape().isRequired, }
ruleActions: shape({
updateMessage: func.isRequired,
}).isRequired,
} }
export default RuleMessage export default RuleMessage

View File

@ -1,5 +1,5 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import ReactTooltip from 'react-tooltip' import ReactTooltip from 'react-tooltip'
@ -8,19 +8,22 @@ import CodeData from 'src/kapacitor/components/CodeData'
import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants' import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {RuleMessage} from 'src/types/kapacitor'
import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (id: string, message: string) => void
}
// needs to be React Component for CodeData click handler to work // needs to be React Component for CodeData click handler to work
@ErrorHandling @ErrorHandling
class RuleMessageTemplates extends Component { class RuleMessageTemplates extends Component<Props> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
handleClickTemplate = template => () => { public render() {
const {updateMessage, rule} = this.props
updateMessage(rule.id, `${rule.message} ${template.label}`)
}
render() {
return ( return (
<div className="rule-section--row rule-section--row-last"> <div className="rule-section--row rule-section--row-last">
<p>Templates:</p> <p>Templates:</p>
@ -41,13 +44,11 @@ class RuleMessageTemplates extends Component {
</div> </div>
) )
} }
}
const {func, shape} = PropTypes private handleClickTemplate = (template: RuleMessage) => () => {
const {updateMessage, rule} = this.props
RuleMessageTemplates.propTypes = { updateMessage(rule.id, `${rule.message} ${template.label}`)
rule: shape().isRequired, }
updateMessage: func.isRequired,
} }
export default RuleMessageTemplates export default RuleMessageTemplates

View File

@ -1,7 +1,13 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
const RuleMessageText = ({rule, updateMessage}) => ( import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (e: ChangeEvent<HTMLTextAreaElement>) => void
}
const RuleMessageText: SFC<Props> = ({rule, updateMessage}) => (
<div className="rule-builder--message"> <div className="rule-builder--message">
<textarea <textarea
className="form-control input-sm form-malachite monotype" className="form-control input-sm form-malachite monotype"
@ -13,11 +19,4 @@ const RuleMessageText = ({rule, updateMessage}) => (
</div> </div>
) )
const {func, shape} = PropTypes
RuleMessageText.propTypes = {
rule: shape().isRequired,
updateMessage: func.isRequired,
}
export default RuleMessageText export default RuleMessageText

View File

@ -1,85 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
import _ from 'lodash'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
const operators = mapToItems(THRESHOLD_OPERATORS, 'operator')
const noopSubmit = e => e.preventDefault()
const getField = ({fields}) => {
const alias = _.get(fields, ['0', 'alias'], false)
if (!alias) {
return _.get(fields, ['0', 'value'], 'Select a Time-Series')
}
return alias
}
const Threshold = ({
rule: {
values: {operator, value, rangeValue},
},
query,
onDropdownChange,
onRuleTypeInputChange,
}) => (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck="false"
value={value}
onChange={onRuleTypeInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/>
{(operator === 'inside range' || operator === 'outside range') && (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck="false"
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)}
</form>
</div>
)
const {shape, string, func} = PropTypes
Threshold.propTypes = {
rule: shape({
values: shape({
operator: string,
rangeOperator: string,
value: string,
rangeValue: string,
}),
}),
onDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
query: shape({}).isRequired,
}
export default Threshold

View File

@ -0,0 +1,122 @@
import React, {Component, FormEvent, ChangeEvent} from 'react'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {getDeep} from 'src/utils/wrappers'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule, QueryConfig} from 'src/types'
interface TypeItem {
type: string
text: string
}
interface Props {
rule: AlertRule
onDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
query: QueryConfig
}
@ErrorHandling
class Threshold extends Component<Props> {
public render() {
const {
rule: {
values: {operator, value},
},
query,
onDropdownChange,
onRuleTypeInputChange,
} = this.props
return (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{this.getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={this.operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={this.noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck={false}
value={value}
onChange={onRuleTypeInputChange}
placeholder={this.firstInputPlaceholder}
/>
{this.secondInput}
</form>
</div>
)
}
private get operators() {
const type = 'operator'
return THRESHOLD_OPERATORS.map(text => {
return {text, type}
})
}
private get isSecondInputRequired() {
const {rule} = this.props
const operator = getDeep<string>(rule, 'values.operator', '')
if (operator === 'inside range' || operator === 'outside range') {
return true
}
return false
}
private get firstInputPlaceholder() {
if (this.isSecondInputRequired) {
return 'lower'
}
return null
}
private get secondInput() {
const {rule, onRuleTypeInputChange} = this.props
const rangeValue = getDeep<string>(rule, 'values.rangeValue', '')
if (this.isSecondInputRequired) {
return (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck={false}
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)
}
}
private noopSubmit = (e: FormEvent<HTMLElement>) => e.preventDefault()
private getField = ({fields}: QueryConfig): string => {
const alias = getDeep<string>(fields, '0.alias', '')
if (!alias) {
return getDeep<string>(fields, '0.value', 'Select a Time-Series')
}
return alias
}
}
export default Threshold

View File

@ -1,6 +1,4 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import Deadman from 'src/kapacitor/components/Deadman' import Deadman from 'src/kapacitor/components/Deadman'
@ -9,7 +7,16 @@ import Relative from 'src/kapacitor/components/Relative'
import DataSection from 'src/kapacitor/components/DataSection' import DataSection from 'src/kapacitor/components/DataSection'
import RuleGraph from 'src/kapacitor/components/RuleGraph' import RuleGraph from 'src/kapacitor/components/RuleGraph'
import {Tab, TabList, TabPanels, TabPanel, Tabs} from 'shared/components/Tabs' import {
Tab,
TabList,
TabPanels,
TabPanel,
Tabs,
} from 'src/shared/components/Tabs'
import {AlertRule, QueryConfig, Source, TimeRange} from 'src/types'
import {KapacitorQueryConfigActions} from 'src/types/actions'
const TABS = ['Threshold', 'Relative', 'Deadman'] const TABS = ['Threshold', 'Relative', 'Deadman']
@ -22,13 +29,36 @@ const handleChooseTrigger = (rule, onChooseTrigger) => triggerIndex => {
const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger)) const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger))
const isDeadman = rule => rule.trigger === 'deadman' const isDeadman = rule => rule.trigger === 'deadman'
const ValuesSection = ({ interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface Props {
rule: AlertRule
onChooseTrigger: () => void
onUpdateValues: () => void
query: QueryConfig
onDeadmanChange: (item: Item) => void
onRuleTypeDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onAddEvery: (frequency: string) => void
onRemoveEvery: () => void
timeRange: TimeRange
queryConfigActions: KapacitorQueryConfigActions
source: Source
onChooseTimeRange: (timeRange: TimeRange) => void
}
const ValuesSection: SFC<Props> = ({
rule, rule,
query, query,
source, source,
timeRange, timeRange,
onAddEvery, onAddEvery,
onRemoveEvery,
onChooseTrigger, onChooseTrigger,
onDeadmanChange, onDeadmanChange,
onChooseTimeRange, onChooseTimeRange,
@ -58,7 +88,6 @@ const ValuesSection = ({
isKapacitorRule={true} isKapacitorRule={true}
actions={queryConfigActions} actions={queryConfigActions}
onAddEvery={onAddEvery} onAddEvery={onAddEvery}
onRemoveEvery={onRemoveEvery}
isDeadman={isDeadman(rule)} isDeadman={isDeadman(rule)}
/> />
</div> </div>
@ -97,24 +126,4 @@ const ValuesSection = ({
</div> </div>
) )
const {shape, string, func} = PropTypes
ValuesSection.propTypes = {
rule: shape({
id: string,
}).isRequired,
onChooseTrigger: func.isRequired,
onUpdateValues: func.isRequired,
query: shape({}).isRequired,
onDeadmanChange: func.isRequired,
onRuleTypeDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
onAddEvery: func.isRequired,
onRemoveEvery: func.isRequired,
timeRange: shape({}).isRequired,
queryConfigActions: shape({}).isRequired,
source: shape({}).isRequired,
onChooseTimeRange: func.isRequired,
}
export default ValuesSection export default ValuesSection

View File

@ -1,26 +1,59 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types' import _ from 'lodash'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view' import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view'
import * as kapacitorQueryConfigActionCreators from 'src/kapacitor/actions/queryConfigs' import * as kapacitorQueryConfigActionCreators from 'src/kapacitor/actions/queryConfigs'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index' import {getActiveKapacitor, getKapacitorConfig} from 'src/shared/apis/index'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import KapacitorRule from 'src/kapacitor/components/KapacitorRule' import KapacitorRule from 'src/kapacitor/components/KapacitorRule'
import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig' import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig'
import {notify as notifyAction} from 'shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import { import {
notifyKapacitorCreateFailed, notifyKapacitorCreateFailed,
notifyCouldNotFindKapacitor, notifyCouldNotFindKapacitor,
} from 'shared/copy/notifications' } from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
Notification,
AlertRule,
QueryConfig,
Kapacitor,
} from 'src/types'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Params {
ruleID: string
}
interface Props {
source: Source
notify: (notification: Notification) => void
rules: AlertRule[]
queryConfigs: QueryConfig[]
ruleActions: KapacitorRuleActions
queryConfigActions: KapacitorQueryConfigActions
params: Params
router: InjectedRouter
}
interface State {
handlersFromConfig: any[]
kapacitor: Kapacitor | {}
}
@ErrorHandling @ErrorHandling
class KapacitorRulePage extends Component { class KapacitorRulePage extends Component<Props, State> {
constructor(props) { constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {
@ -29,7 +62,7 @@ class KapacitorRulePage extends Component {
} }
} }
async componentDidMount() { public async componentDidMount() {
const {params, source, ruleActions, notify} = this.props const {params, source, ruleActions, notify} = this.props
if (params.ruleID === 'new') { if (params.ruleID === 'new') {
@ -54,9 +87,8 @@ class KapacitorRulePage extends Component {
} }
} }
render() { public render() {
const { const {
rules,
params, params,
source, source,
router, router,
@ -65,8 +97,7 @@ class KapacitorRulePage extends Component {
queryConfigActions, queryConfigActions,
} = this.props } = this.props
const {handlersFromConfig, kapacitor} = this.state const {handlersFromConfig, kapacitor} = this.state
const rule = const rule = this.rule
params.ruleID === 'new' ? rules[DEFAULT_RULE_ID] : rules[params.ruleID]
const query = rule && queryConfigs[rule.queryID] const query = rule && queryConfigs[rule.queryID]
if (!query) { if (!query) {
@ -84,41 +115,26 @@ class KapacitorRulePage extends Component {
ruleID={params.ruleID} ruleID={params.ruleID}
router={router} router={router}
kapacitor={kapacitor} kapacitor={kapacitor}
configLink={`/sources/${source.id}/kapacitors/${kapacitor.id}/edit`} configLink={`/sources/${source.id}/kapacitors/${this.kapacitorID}/edit`}
/> />
) )
} }
}
const {func, shape, string} = PropTypes private get kapacitorID(): string {
const {kapacitor} = this.state
return _.get(kapacitor, 'id')
}
KapacitorRulePage.propTypes = { private get rule(): AlertRule {
source: shape({ const {params, rules} = this.props
links: shape({ const ruleID = _.get(params, 'ruleID')
proxy: string.isRequired,
self: string.isRequired, if (ruleID === 'new') {
}), return rules[DEFAULT_RULE_ID]
}), }
notify: func,
rules: shape({}).isRequired, return rules[params.ruleID]
queryConfigs: shape({}).isRequired, }
ruleActions: shape({
loadDefaultRule: func.isRequired,
fetchRule: func.isRequired,
chooseTrigger: func.isRequired,
addEvery: func.isRequired,
removeEvery: func.isRequired,
updateRuleValues: func.isRequired,
updateMessage: func.isRequired,
updateRuleName: func.isRequired,
}).isRequired,
queryConfigActions: shape({}).isRequired,
params: shape({
ruleID: string,
}).isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
} }
const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({ const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({

View File

@ -48,7 +48,7 @@ interface TabListProps {
activeIndex?: number activeIndex?: number
onActivate?: (index: number) => void onActivate?: (index: number) => void
isKapacitorTabs?: string isKapacitorTabs?: string
customClass: string customClass?: string
} }
export const TabList: SFC<TabListProps> = ({ export const TabList: SFC<TabListProps> = ({
@ -97,7 +97,7 @@ TabList.defaultProps = {
interface TabPanelsProps { interface TabPanelsProps {
children: JSX.Element[] | JSX.Element children: JSX.Element[] | JSX.Element
activeIndex?: number activeIndex?: number
customClass: string customClass?: string
} }
export const TabPanels: SFC<TabPanelsProps> = ({ export const TabPanels: SFC<TabPanelsProps> = ({
@ -120,7 +120,7 @@ export const TabPanel: SFC<TabPanelProps> = ({children}) => (
interface TabsProps { interface TabsProps {
children: JSX.Element[] | JSX.Element children: JSX.Element[] | JSX.Element
onSelect?: (activeIndex: number) => void onSelect?: (activeIndex: number) => void
tabContentsClass: string tabContentsClass?: string
tabsClass?: string tabsClass?: string
initialIndex?: number initialIndex?: number
} }

View File

@ -1,4 +1,8 @@
import * as kapacitorQueryConfigActions from 'src/kapacitor/actions/queryConfigs' import * as kapacitorQueryConfigActions from 'src/kapacitor/actions/queryConfigs'
import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view'
type KapacitorQueryConfigActions = typeof kapacitorQueryConfigActions type KapacitorQueryConfigActions = typeof kapacitorQueryConfigActions
type KapacitorRuleActions = typeof kapacitorRuleActionCreators
export {KapacitorQueryConfigActions} export {KapacitorQueryConfigActions}
export {KapacitorRuleActions}

View File

@ -32,6 +32,7 @@ export interface AlertRule {
error: string error: string
created: string created: string
modified: string modified: string
queryID?: string
'last-enabled'?: string 'last-enabled'?: string
} }
@ -224,7 +225,7 @@ export interface RuleMessageTemplate {
time: RuleMessage time: RuleMessage
} }
interface RuleMessage { export interface RuleMessage {
label: string label: string
text: string text: string
} }