Merge pull request #1971 from influxdata/bugfix/deadman

BUGFIX: deadman
pull/10616/head
Deniz Kusefoglu 2017-09-08 14:30:11 -04:00 committed by GitHub
commit bd88803f9f
11 changed files with 506 additions and 476 deletions

View File

@ -25,6 +25,7 @@
1. [#1942](https://github.com/influxdata/chronograf/pull/1942): Polish appearance of optional alert parameters in Kapacitor rule builder
1. [#1944](https://github.com/influxdata/chronograf/pull/1944): Add active state for Status page navbar icon
1. [#1944](https://github.com/influxdata/chronograf/pull/1944): Improve UX of navigation to a sub-nav item in the navbar
1. [#1971](https://github.com/influxdata/chronograf/pull/1971): Resolve confusing deadman trigger alert rule UI
## v1.3.7.0 [2017-08-23]
### Features

View File

@ -1,6 +1,4 @@
import React, {PropTypes} from 'react'
import buildInfluxQLQuery from 'utils/influxql'
import classnames from 'classnames'
import React, {PropTypes, Component} from 'react'
import DatabaseList from 'src/shared/components/DatabaseList'
import MeasurementList from 'src/shared/components/MeasurementList'
@ -8,112 +6,53 @@ import FieldList from 'src/shared/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
export const DataSection = React.createClass({
propTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
kapacitors: PropTypes.string.isRequired,
}).isRequired,
}),
query: PropTypes.shape({
id: PropTypes.string.isRequired,
}).isRequired,
addFlashMessage: PropTypes.func,
actions: PropTypes.shape({
chooseNamespace: PropTypes.func.isRequired,
chooseMeasurement: PropTypes.func.isRequired,
applyFuncsToField: PropTypes.func.isRequired,
chooseTag: PropTypes.func.isRequired,
groupByTag: PropTypes.func.isRequired,
toggleField: PropTypes.func.isRequired,
groupByTime: PropTypes.func.isRequired,
toggleTagAcceptance: PropTypes.func.isRequired,
}).isRequired,
onAddEvery: PropTypes.func.isRequired,
onRemoveEvery: PropTypes.func.isRequired,
timeRange: PropTypes.shape({}).isRequired,
isKapacitorRule: PropTypes.bool,
},
class DataSection extends Component {
constructor(props) {
super(props)
}
childContextTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
self: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
},
getChildContext() {
return {source: this.props.source}
},
handleChooseNamespace(namespace) {
handleChooseNamespace = namespace => {
this.props.actions.chooseNamespace(this.props.query.id, namespace)
},
}
handleChooseMeasurement(measurement) {
handleChooseMeasurement = measurement => {
this.props.actions.chooseMeasurement(this.props.query.id, measurement)
},
}
handleToggleField(field) {
handleToggleField = field => {
this.props.actions.toggleField(this.props.query.id, field)
// Every is only added when a function has been added to a field.
// Here, the field is selected without a function.
this.props.onRemoveEvery()
// Because there are no functions there is no group by time.
this.props.actions.groupByTime(this.props.query.id, null)
},
}
handleGroupByTime(time) {
handleGroupByTime = time => {
this.props.actions.groupByTime(this.props.query.id, time)
},
}
handleApplyFuncsToField(fieldFunc) {
handleApplyFuncsToField = fieldFunc => {
this.props.actions.applyFuncsToField(this.props.query.id, fieldFunc)
this.props.onAddEvery(defaultEveryFrequency)
},
}
handleChooseTag(tag) {
handleChooseTag = tag => {
this.props.actions.chooseTag(this.props.query.id, tag)
},
}
handleToggleTagAcceptance() {
handleToggleTagAcceptance = () => {
this.props.actions.toggleTagAcceptance(this.props.query.id)
},
}
handleGroupByTag(tagKey) {
handleGroupByTag = tagKey => {
this.props.actions.groupByTag(this.props.query.id, tagKey)
},
}
render() {
const {query, timeRange: {lower}} = this.props
const statement = query.rawText || buildInfluxQLQuery({lower}, query)
const {query, isKapacitorRule, isDeadman} = this.props
return (
<div className="rule-section">
<h3 className="rule-section--heading">Select a Time Series</h3>
<div className="rule-section--body">
<pre className="rule-section--border-bottom">
<code
className={classnames({
'metric-placeholder': !statement,
})}
>
{statement || 'Build a query below'}
</code>
</pre>
{this.renderQueryBuilder()}
</div>
</div>
)
},
renderQueryBuilder() {
const {query, isKapacitorRule} = this.props
return (
<div className="query-builder">
<div className="query-builder rule-section--border-bottom">
<DatabaseList
query={query}
onChooseNamespace={this.handleChooseNamespace}
@ -125,16 +64,47 @@ export const DataSection = React.createClass({
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
/>
<FieldList
query={query}
onToggleField={this.handleToggleField}
onGroupByTime={this.handleGroupByTime}
applyFuncsToField={this.handleApplyFuncsToField}
isKapacitorRule={isKapacitorRule}
/>
{isDeadman
? null
: <FieldList
query={query}
onToggleField={this.handleToggleField}
onGroupByTime={this.handleGroupByTime}
applyFuncsToField={this.handleApplyFuncsToField}
isKapacitorRule={isKapacitorRule}
/>}
</div>
)
},
})
}
}
const {string, func, shape, bool} = PropTypes
DataSection.propTypes = {
source: shape({
links: shape({
kapacitors: string.isRequired,
}).isRequired,
}),
query: shape({
id: string.isRequired,
}).isRequired,
addFlashMessage: func,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
applyFuncsToField: func.isRequired,
chooseTag: func.isRequired,
groupByTag: func.isRequired,
toggleField: func.isRequired,
groupByTime: func.isRequired,
toggleTagAcceptance: func.isRequired,
}).isRequired,
onAddEvery: func.isRequired,
onRemoveEvery: func.isRequired,
timeRange: shape({}).isRequired,
isKapacitorRule: bool,
isDeadman: bool,
}
export default DataSection

View File

@ -0,0 +1,32 @@
import React, {PropTypes} from 'react'
import {PERIODS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
const periods = PERIODS.map(text => {
return {text}
})
const Deadman = ({rule, onChange}) =>
<div className="rule-section--row">
<p>Send Alert if Data is missing for</p>
<Dropdown
className="dropdown-80"
menuClass="dropdown-malachite"
items={periods}
selected={rule.values.period}
onChoose={onChange}
/>
</div>
const {shape, string, func} = PropTypes
Deadman.propTypes = {
rule: shape({
values: shape({
period: string,
}),
}),
onChange: func.isRequired,
}
export default Deadman

View File

@ -1,9 +1,7 @@
import React, {PropTypes} from 'react'
import React, {PropTypes, Component} from 'react'
import DataSection from 'src/kapacitor/components/DataSection'
import ValuesSection from 'src/kapacitor/components/ValuesSection'
import RuleHeader from 'src/kapacitor/components/RuleHeader'
import RuleGraph from 'src/kapacitor/components/RuleGraph'
import RuleMessage from 'src/kapacitor/components/RuleMessage'
import FancyScrollbar from 'shared/components/FancyScrollbar'
@ -11,100 +9,20 @@ import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql'
import timeRanges from 'hson!shared/data/timeRanges.hson'
export const KapacitorRule = React.createClass({
propTypes: {
source: PropTypes.shape({}).isRequired,
rule: PropTypes.shape({}).isRequired,
query: PropTypes.shape({}).isRequired,
queryConfigs: PropTypes.shape({}).isRequired,
queryConfigActions: PropTypes.shape({}).isRequired,
ruleActions: PropTypes.shape({}).isRequired,
addFlashMessage: PropTypes.func.isRequired,
isEditing: PropTypes.bool.isRequired,
enabledAlerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
router: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
kapacitor: PropTypes.shape({}).isRequired,
},
getInitialState() {
return {
class KapacitorRule extends Component {
constructor(props) {
super(props)
this.state = {
timeRange: timeRanges.find(tr => tr.lower === 'now() - 15m'),
}
},
}
render() {
const {
queryConfigActions,
source,
enabledAlerts,
queryConfigs,
query,
rule,
ruleActions,
isEditing,
} = this.props
const {chooseTrigger, updateRuleValues} = ruleActions
const {timeRange} = this.state
return (
<div className="page">
<RuleHeader
rule={rule}
actions={ruleActions}
onSave={isEditing ? this.handleEdit : this.handleCreate}
onChooseTimeRange={this.handleChooseTimeRange}
validationError={this.validationError()}
timeRange={timeRange}
source={source}
/>
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
<div className="container-fluid">
<div className="row">
<div className="col-xs-12">
<div className="rule-builder">
<DataSection
timeRange={timeRange}
source={source}
query={query}
actions={queryConfigActions}
onAddEvery={this.handleAddEvery}
onRemoveEvery={this.handleRemoveEvery}
isKapacitorRule={true}
/>
<ValuesSection
rule={rule}
query={queryConfigs[rule.queryID]}
onChooseTrigger={chooseTrigger}
onUpdateValues={updateRuleValues}
/>
<RuleGraph
timeRange={timeRange}
source={source}
query={query}
rule={rule}
/>
<RuleMessage
rule={rule}
actions={ruleActions}
enabledAlerts={enabledAlerts}
/>
</div>
</div>
</div>
</div>
</FancyScrollbar>
</div>
)
},
handleChooseTimeRange({lower}) {
handleChooseTimeRange = ({lower}) => {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
},
}
handleCreate() {
handleCreate = () => {
const {
addFlashMessage,
queryConfigs,
@ -130,9 +48,9 @@ export const KapacitorRule = React.createClass({
text: 'There was a problem creating the rule',
})
})
},
}
handleEdit() {
handleEdit = () => {
const {addFlashMessage, queryConfigs, rule} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
@ -148,19 +66,19 @@ export const KapacitorRule = React.createClass({
text: 'There was a problem updating the rule',
})
})
},
}
handleAddEvery(frequency) {
handleAddEvery = frequency => {
const {rule: {id: ruleID}, ruleActions: {addEvery}} = this.props
addEvery(ruleID, frequency)
},
}
handleRemoveEvery() {
handleRemoveEvery = () => {
const {rule: {id: ruleID}, ruleActions: {removeEvery}} = this.props
removeEvery(ruleID)
},
}
validationError() {
validationError = () => {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
@ -175,16 +93,115 @@ export const KapacitorRule = React.createClass({
}
return ''
},
}
deadmanValidation() {
deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return 'Deadman requires a database and measurement'
}
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 {
rule,
source,
isEditing,
ruleActions,
queryConfigs,
enabledAlerts,
queryConfigActions,
} = this.props
const {chooseTrigger, updateRuleValues} = ruleActions
const {timeRange} = this.state
return (
<div className="page">
<RuleHeader
rule={rule}
actions={ruleActions}
onSave={isEditing ? this.handleEdit : this.handleCreate}
onChooseTimeRange={this.handleChooseTimeRange}
validationError={this.validationError()}
timeRange={timeRange}
source={source}
/>
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
<div className="container-fluid">
<div className="row">
<div className="col-xs-12">
<div className="rule-builder">
<ValuesSection
rule={rule}
source={source}
timeRange={timeRange}
onChooseTrigger={chooseTrigger}
onAddEvery={this.handleAddEvery}
onUpdateValues={updateRuleValues}
query={queryConfigs[rule.queryID]}
onRemoveEvery={this.handleRemoveEvery}
queryConfigActions={queryConfigActions}
onDeadmanChange={this.handleDeadmanChange}
onRuleTypeInputChange={this.handleRuleTypeInputChange}
onRuleTypeDropdownChange={this.handleRuleTypeDropdownChange}
/>
<RuleMessage
rule={rule}
actions={ruleActions}
enabledAlerts={enabledAlerts}
/>
</div>
</div>
</div>
</div>
</FancyScrollbar>
</div>
)
}
}
KapacitorRule.propTypes = {
source: PropTypes.shape({}).isRequired,
rule: PropTypes.shape({
values: PropTypes.shape({}),
}).isRequired,
query: PropTypes.shape({}).isRequired,
queryConfigs: PropTypes.shape({}).isRequired,
queryConfigActions: PropTypes.shape({}).isRequired,
ruleActions: PropTypes.shape({}).isRequired,
addFlashMessage: PropTypes.func.isRequired,
isEditing: PropTypes.bool.isRequired,
enabledAlerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
router: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
kapacitor: PropTypes.shape({}).isRequired,
}
export default KapacitorRule

View File

@ -0,0 +1,70 @@
import React, {PropTypes} from 'react'
import {CHANGES, OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
const changes = mapToItems(CHANGES, 'change')
const shifts = mapToItems(SHIFTS, 'shift')
const operators = mapToItems(OPERATORS, 'operator')
const Relative = ({
onRuleTypeInputChange,
onDropdownChange,
rule: {values: {change, shift, operator, value}},
}) =>
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert when</p>
<Dropdown
className="dropdown-110"
menuClass="dropdown-malachite"
items={changes}
selected={change}
onChoose={onDropdownChange}
/>
<p>compared to previous</p>
<Dropdown
className="dropdown-80"
menuClass="dropdown-malachite"
items={shifts}
selected={shift}
onChoose={onDropdownChange}
/>
<p>is</p>
<Dropdown
className="dropdown-160"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck="false"
value={value}
onChange={onRuleTypeInputChange}
required={true}
/>
</form>
{change === CHANGES[1] ? <p>%</p> : null}
</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

View File

@ -0,0 +1,72 @@
import React, {PropTypes} from 'react'
import {OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
const operators = mapToItems(OPERATORS, 'operator')
const Threshold = ({
rule: {values: {operator, value, rangeValue}},
query,
onDropdownChange,
onRuleTypeInputChange,
}) =>
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">
{query.fields.length ? query.fields[0].field : 'Select a Time-Series'}
</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}}>
<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

@ -1,253 +1,115 @@
import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'
import {Tab, TabList, TabPanels, TabPanel, Tabs} from 'shared/components/Tabs'
import {OPERATORS, PERIODS, CHANGES, SHIFTS} from 'src/kapacitor/constants'
import _ from 'lodash'
import Deadman from 'src/kapacitor/components/Deadman'
import Threshold from 'src/kapacitor/components/Threshold'
import Relative from 'src/kapacitor/components/Relative'
import DataSection from 'src/kapacitor/components/DataSection'
import RuleGraph from 'src/kapacitor/components/RuleGraph'
import {Tab, TabList, TabPanels, TabPanel, Tabs} from 'shared/components/Tabs'
const TABS = ['Threshold', 'Relative', 'Deadman']
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
export const ValuesSection = React.createClass({
propTypes: {
rule: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
onChooseTrigger: PropTypes.func.isRequired,
onUpdateValues: PropTypes.func.isRequired,
query: PropTypes.shape({}).isRequired,
},
const handleChooseTrigger = (rule, onChooseTrigger) => triggerIndex => {
if (TABS[triggerIndex] === rule.trigger) {
return
}
return onChooseTrigger(rule.id, TABS[triggerIndex])
}
const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger))
const isDeadman = rule => rule.trigger === 'deadman'
render() {
const {rule, query} = this.props
const initialIndex = TABS.indexOf(_.startCase(rule.trigger))
return (
<div className="rule-section">
<h3 className="rule-section--heading">Rule Conditions</h3>
<div className="rule-section--body">
<Tabs initialIndex={initialIndex} onSelect={this.handleChooseTrigger}>
<TabList isKapacitorTabs="true">
{TABS.map(tab =>
<Tab key={tab} isKapacitorTab={true}>
{tab}
</Tab>
)}
</TabList>
<TabPanels>
<TabPanel>
<Threshold
rule={rule}
query={query}
onChange={this.handleValuesChange}
/>
</TabPanel>
<TabPanel>
<Relative rule={rule} onChange={this.handleValuesChange} />
</TabPanel>
<TabPanel>
<Deadman rule={rule} onChange={this.handleValuesChange} />
</TabPanel>
</TabPanels>
</Tabs>
const ValuesSection = ({
rule,
query,
source,
timeRange,
onAddEvery,
onRemoveEvery,
onDeadmanChange,
queryConfigActions,
onRuleTypeInputChange,
onRuleTypeDropdownChange,
onChooseTrigger,
}) =>
<div className="rule-section">
<h3 className="rule-section--heading">Alert Type</h3>
<div className="rule-section--body">
<Tabs
initialIndex={initialIndex(rule)}
onSelect={handleChooseTrigger(rule, onChooseTrigger)}
>
<TabList isKapacitorTabs="true">
{TABS.map(tab =>
<Tab key={tab} isKapacitorTab={true}>
{tab}
</Tab>
)}
</TabList>
<div>
<h3 className="rule-builder--sub-header">Time Series</h3>
<DataSection
query={query}
timeRange={timeRange}
isKapacitorRule={true}
actions={queryConfigActions}
onAddEvery={onAddEvery}
onRemoveEvery={onRemoveEvery}
isDeadman={isDeadman(rule)}
/>
</div>
</div>
)
},
<h3 className="rule-builder--sub-header">Rule Conditions</h3>
<TabPanels>
<TabPanel>
<Threshold
rule={rule}
query={query}
onDropdownChange={onRuleTypeDropdownChange}
onRuleTypeInputChange={onRuleTypeInputChange}
/>
</TabPanel>
<TabPanel>
<Relative
rule={rule}
onDropdownChange={onRuleTypeDropdownChange}
onRuleTypeInputChange={onRuleTypeInputChange}
/>
</TabPanel>
<TabPanel>
<Deadman rule={rule} onChange={onDeadmanChange} />
</TabPanel>
</TabPanels>
{isDeadman(rule)
? null
: <RuleGraph
rule={rule}
query={query}
source={source}
timeRange={timeRange}
/>}
</Tabs>
</div>
</div>
handleChooseTrigger(triggerIndex) {
const {rule, onChooseTrigger} = this.props
if (TABS[triggerIndex] === rule.trigger) {
return
}
const {shape, string, func} = PropTypes
onChooseTrigger(rule.id, TABS[triggerIndex])
},
handleValuesChange(values) {
const {onUpdateValues, rule} = this.props
onUpdateValues(rule.id, rule.trigger, values)
},
})
const Threshold = React.createClass({
propTypes: {
rule: PropTypes.shape({
values: PropTypes.shape({
operator: PropTypes.string,
rangeOperator: PropTypes.string,
value: PropTypes.string,
rangeValue: PropTypes.string,
}),
}),
onChange: PropTypes.func.isRequired,
query: PropTypes.shape({}).isRequired,
},
handleDropdownChange(item) {
this.props.onChange({...this.props.rule.values, [item.type]: item.text})
},
handleInputChange() {
this.props.onChange({
...this.props.rule.values,
value: this.valueInput.value,
rangeValue: this.valueRangeInput ? this.valueRangeInput.value : '',
})
},
render() {
const {operator, value, rangeValue} = this.props.rule.values
const {query} = this.props
const operators = mapToItems(OPERATORS, 'operator')
return (
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">
{query.fields.length ? query.fields[0].field : 'Select a Time-Series'}
</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={this.handleDropdownChange}
/>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
type="text"
spellCheck="false"
ref={r => (this.valueInput = r)}
defaultValue={value}
onKeyUp={this.handleInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/>
{(operator === 'inside range' || operator === 'outside range') &&
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck="false"
ref={r => (this.valueRangeInput = r)}
defaultValue={rangeValue}
onKeyUp={this.handleInputChange}
/>}
</div>
)
},
})
const Relative = React.createClass({
propTypes: {
rule: PropTypes.shape({
values: PropTypes.shape({
change: PropTypes.string,
shift: PropTypes.string,
operator: PropTypes.string,
value: PropTypes.string,
}),
}),
onChange: PropTypes.func.isRequired,
},
handleDropdownChange(item) {
this.props.onChange({...this.props.rule.values, [item.type]: item.text})
},
handleInputChange() {
this.props.onChange({...this.props.rule.values, value: this.input.value})
},
render() {
const {change, shift, operator, value} = this.props.rule.values
const changes = mapToItems(CHANGES, 'change')
const shifts = mapToItems(SHIFTS, 'shift')
const operators = mapToItems(OPERATORS, 'operator')
return (
<div className="rule-section--row rule-section--border-bottom">
<p>Send Alert when</p>
<Dropdown
className="dropdown-110"
menuClass="dropdown-malachite"
items={changes}
selected={change}
onChoose={this.handleDropdownChange}
/>
<p>compared to previous</p>
<Dropdown
className="dropdown-80"
menuClass="dropdown-malachite"
items={shifts}
selected={shift}
onChoose={this.handleDropdownChange}
/>
<p>is</p>
<Dropdown
className="dropdown-160"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={this.handleDropdownChange}
/>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px'}}
ref={r => (this.input = r)}
defaultValue={value}
onKeyUp={this.handleInputChange}
required={true}
type="text"
spellCheck="false"
/>
{change === CHANGES[1] ? <p>%</p> : null}
</div>
)
},
})
const Deadman = React.createClass({
propTypes: {
rule: PropTypes.shape({
values: PropTypes.shape({
period: PropTypes.string,
}),
}),
onChange: PropTypes.func.isRequired,
},
handleChange(item) {
this.props.onChange({period: item.text})
},
render() {
const periods = PERIODS.map(text => {
return {text}
})
return (
<div className="rule-section--row">
<p>Send Alert if Data is missing for</p>
<Dropdown
className="dropdown-80"
menuClass="dropdown-malachite"
items={periods}
selected={this.props.rule.values.period}
onChoose={this.handleChange}
/>
</div>
)
},
})
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,
}
export default ValuesSection

View File

@ -18,8 +18,6 @@ class KapacitorRulePage extends Component {
enabledAlerts: [],
kapacitor: {},
}
this.isEditing = ::this.isEditing
}
async componentDidMount() {
@ -97,7 +95,7 @@ class KapacitorRulePage extends Component {
)
}
isEditing() {
isEditing = () => {
const {params} = this.props
return params.ruleID && params.ruleID !== 'new'
}

View File

@ -59,7 +59,7 @@ export const TabList = React.createClass({
if (this.props.isKapacitorTabs === 'true') {
return (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Alert Type</p>
<p>Choose One:</p>
<div className="nav nav-tablist nav-tablist-sm nav-tablist-malachite">
{children}
</div>
@ -147,11 +147,13 @@ export const Tabs = React.createClass({
render() {
const children = React.Children.map(this.props.children, child => {
if (child.type === TabPanels) {
if (child && child.type === TabPanels) {
return React.cloneElement(child, {
activeIndex: this.state.activeIndex,
})
} else if (child.type === TabList) {
}
if (child && child.type === TabList) {
return React.cloneElement(child, {
activeIndex: this.state.activeIndex,
onActivate: this.handleActivateTab,

View File

@ -23,7 +23,7 @@ const getRange = (
const subPad = bigNum => bigNum.times(SUB_FACTOR).toNumber()
const pad = v => {
if (v === null || v === '') {
if (v === null || v === '' || v === undefined) {
return null
}

View File

@ -29,7 +29,9 @@ $rule-builder--radius-lg: 5px;
color: $g15-platinum;
@include no-user-select();
&:first-child {margin-left: 0;}
&:first-child {
margin-left: 0;
}
}
/*
@ -59,7 +61,8 @@ $rule-builder--radius-lg: 5px;
}
.rule-section--heading {
margin: 0;
padding: $rule-builder--section-gap 0 $rule-builder--padding-md $rule-builder--left-gutter;
padding: $rule-builder--section-gap 0 $rule-builder--padding-md
$rule-builder--left-gutter;
font-size: $page-header-size;
font-weight: $page-header-weight;
color: $g12-forge;
@ -127,7 +130,6 @@ $rule-builder--radius-lg: 5px;
margin-left: $rule-builder--padding-sm - 2px;
}
/*
Section 1 - Select a Time Series
-----------------------------------------------------------------------------
@ -157,10 +159,9 @@ $rule-builder--radius-lg: 5px;
}
.query-builder--column {
margin-right: 2px;
&:last-child {margin-right: 0;}
}
.query-builder--column:nth-child(1) .query-builder--list {
border-bottom-left-radius: $rule-builder--radius-lg;
&:last-child {
margin-right: 0;
}
}
.query-builder--column:nth-child(4) .query-builder--list,
.query-builder--column:nth-child(4) .query-builder--list-empty {
@ -175,7 +176,9 @@ $rule-builder--radius-lg: 5px;
}
.group-by-tag.active {
background-color: $c-rainforest !important;
&:hover {background-color: $c-honeydew !important;}
&:hover {
background-color: $c-honeydew !important;
}
}
.query-builder--list-item .query-builder--checkbox:after {
background-color: $c-rainforest;
@ -199,6 +202,13 @@ $rule-builder--radius-lg: 5px;
Sectiom 2 - Rule Conditions
-----------------------------------------------------------------------------
*/
.rule-builder--sub-header {
margin: 0;
padding: 30px 0 13px 0;
font-size: 19px;
font-weight: 400 !important;
color: #a4a8b6;
}
.rule-builder--metric {
height: 30px;
line-height: 30px;
@ -211,8 +221,6 @@ $rule-builder--radius-lg: 5px;
@include no-user-select();
}
.rule-builder--graph {
margin-left: $rule-builder--left-gutter;
width: calc(100% - #{$rule-builder--left-gutter});
background-color: $rule-builder--section-bg;
border-radius: 0 0 $rule-builder--radius-lg $rule-builder--radius-lg;
padding: 0 $rule-builder--padding-sm;
@ -234,17 +242,6 @@ $rule-builder--radius-lg: 5px;
}
}
&:before {
content: '';
display: block;
position: absolute;
transform: translateX(-50%);
width: $rule-builder--accent-line-width;
height: 100%;
background-color: $rule-builder--accent-line-color;
top: 0;
left: (($rule-builder--dot / 2) - $rule-builder--left-gutter);
}
.container--dygraph-legend {
transform: translateX(-50%);
background-color: $g5-pepper;
@ -294,8 +291,12 @@ textarea.rule-builder--message {
border-radius: 0;
@include custom-scrollbar($rule-builder--section-bg,$rule-builder--accent-color);
&:hover {border-color: $g4-onyx;}
&:focus {background-color: $rule-builder--section-bg;}
&:hover {
border-color: $g4-onyx;
}
&:focus {
background-color: $rule-builder--section-bg;
}
}
.rule-builder--message-template {
height: 30px;
@ -348,9 +349,7 @@ textarea.rule-builder--message {
margin: 0;
height: 18px;
line-height: 20px;
transition:
background-color 0.4s ease,
color 0.4s ease,
transition: background-color 0.4s ease, color 0.4s ease,
box-shadow 0.4s ease;
}
.form-group > input.form-control:hover + label {
@ -364,18 +363,25 @@ textarea.rule-builder--message {
}
}
/*
Color coding for alerts in Alert History table
-----------------------------------------------------------------------------
*/
.alert-level-ok {
&, &:hover {color: $c-rainforest !important;}
&,
&:hover {
color: $c-rainforest !important;
}
}
.alert-level-warning {
&, &:hover {color: $c-pineapple !important;}
&,
&:hover {
color: $c-pineapple !important;
}
}
.alert-level-critical {
&, &:hover {color: $c-dreamsicle !important;}
&,
&:hover {
color: $c-dreamsicle !important;
}
}