First attempt to make an alert rule builder

pull/10616/head
Will Piers 2016-10-27 18:48:13 -07:00
parent bbf73ab09e
commit 67c8343417
5 changed files with 314 additions and 4 deletions

View File

@ -7,7 +7,7 @@ import App from 'src/App';
import AlertsApp from 'src/alerts';
import CheckDataNodes from 'src/CheckDataNodes';
import {HostsPage, HostPage} from 'src/hosts';
import {KapacitorPage, KapacitorTasksPage} from 'src/kapacitor';
import {KapacitorPage, KapacitorRulePage, KapacitorTasksPage} from 'src/kapacitor';
import QueriesPage from 'src/queries';
import TasksPage from 'src/tasks';
import RetentionPoliciesPage from 'src/retention_policies';
@ -117,6 +117,7 @@ const Root = React.createClass({
<Route path="kapacitor-config" component={KapacitorPage} />
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
<Route path="alerts" component={AlertsApp} />
<Route path="alert-rules" component={KapacitorRulePage} />
</Route>
<Route path="tasks" component={TasksPage} />
<Route path="*" component={NotFound} />

View File

@ -0,0 +1,207 @@
import React, {PropTypes} from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import selectStatement from '../../chronograf/utils/influxql/select';
import DatabaseList from '../../chronograf/components/DatabaseList';
import MeasurementList from '../../chronograf/components/MeasurementList';
import FieldList from '../../chronograf/components/FieldList';
import TagList from '../../chronograf/components/TagList';
const DB_TAB = 'databases';
const MEASUREMENTS_TAB = 'measurments';
const FIELDS_TAB = 'fields';
const TAGS_TAB = 'tags';
export const DataSection = React.createClass({
propTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
kapacitors: PropTypes.string.isRequired,
}).isRequired,
}),
addFlashMessage: PropTypes.func,
},
childContextTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
self: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
},
getChildContext() {
return {source: this.props.source};
},
getInitialState() {
return {
activeTab: DB_TAB,
query: {
id: 1,
database: null,
measurement: null,
retentionPolicy: null,
fields: [],
tags: {},
groupBy: {
time: null,
tags: [],
},
areTagsAccepted: true,
rawText: null,
},
};
},
handleChooseNamespace(namespace) {
this.setState((oldState) => {
const newQuery = Object.assign({}, oldState.query, namespace);
return Object.assign({}, oldState, {query: newQuery, activeTab: MEASUREMENTS_TAB});
});
},
handleChooseMeasurement(measurement) {
this.setState((oldState) => {
const newQuery = Object.assign({}, oldState.query, {measurement});
return Object.assign({}, oldState, {query: newQuery, activeTab: FIELDS_TAB});
});
},
handleToggleField({field, funcs}) {
this.setState((oldState) => {
const isSelected = oldState.query.fields.find((f) => f.field === field);
if (isSelected) {
const nextFields = oldState.query.fields.filter((f) => f.field !== field);
if (!nextFields.length) {
const nextGroupBy = Object.assign({}, oldState.query.groupBy, {time: null});
return Object.assign({}, oldState, {
query: Object.assign({}, oldState.query, {
fields: nextFields,
groupBy: nextGroupBy,
}),
});
}
return Object.assign({}, oldState, {
query: Object.assign({}, oldState.query, {
fields: nextFields,
}),
});
}
return Object.assign({}, oldState, {
query: Object.assign({}, oldState.query, {
fields: oldState.query.fields.concat({field, funcs}),
}),
})
return update(queryConfig, {fields: {$push: [fieldFunc]}});
});
},
handleGroupByTime(time) {
},
handleApplyFuncsToField(fieldFunc) {
},
handleChooseTag(tag) {
},
handleToggleTagAcceptance() {
},
handleGroupByTag(tagKey) {
},
handleClickTab(tab) {
this.setState({activeTab: tab});
},
render() {
const {query} = this.state;
const timeRange = {lower: 'now() - 15m', upper: null};
const statement = query.rawText || selectStatement(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
return (
<div className="query-editor">
<div className="query-editor__code">
<pre className={classNames("", {"rq-mode": query.rawText})}><code>{statement}</code></pre>
</div>
{this.renderEditor()}
</div>
);
},
renderEditor() {
const {query, activeTab} = this.state;
if (query.rawText) {
return (
<div className="generic-empty-state query-editor-empty-state">
<p className="margin-bottom-zero">
<span className="icon alert-triangle"></span>
&nbsp;Only editable in the <strong>Raw Query</strong> tab.
</p>
</div>
);
}
return (
<div>
<div className="query-editor__tabs">
<div className="query-editor__tabs-heading">Schema Explorer</div>
<div onClick={_.wrap(DB_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === DB_TAB})}>Databases</div>
<div onClick={_.wrap(MEASUREMENTS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === MEASUREMENTS_TAB})}>Measurements</div>
<div onClick={_.wrap(FIELDS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === FIELDS_TAB})}>Fields</div>
<div onClick={_.wrap(TAGS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === TAGS_TAB})}>Tags</div>
</div>
{this.renderList()}
</div>
);
},
renderList() {
const {query} = this.state;
switch (this.state.activeTab) {
case DB_TAB:
return (
<DatabaseList
query={query}
onChooseNamespace={this.handleChooseNamespace}
/>
);
case MEASUREMENTS_TAB:
return (
<MeasurementList
query={query}
onChooseMeasurement={this.handleChooseMeasurement}
/>
);
case FIELDS_TAB:
return (
<FieldList
query={query}
onToggleField={this.handleToggleField}
onGroupByTime={this.handleGroupByTime}
applyFuncsToField={this.handleApplyFuncsToField}
/>
);
case TAGS_TAB:
return (
<TagList
query={query}
onChooseTag={this.handleChooseTag}
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
/>
);
default:
return <ul className="query-editor__list"></ul>;
}
},
});
export default DataSection;

View File

@ -0,0 +1,101 @@
import React, {PropTypes} from 'react';
import DataSection from '../components/DataSection';
const DATA_SECTION = 'data';
const VALUES_SECTION = 'values';
const MESSAGE_SECTION = 'message';
const ALERTS_SECTION = 'alerts';
export const KapacitorRulePage = React.createClass({
propTypes: {
source: PropTypes.shape({
links: PropTypes.shape({
self: PropTypes.string.isRequired,
}).isRequired,
}),
addFlashMessage: PropTypes.func,
},
getInitialState() {
return {
activeSection: DATA_SECTION,
};
},
render() {
return (
<div className="kapacitor-rule-page">
<div className="enterprise-header">
<div className="enterprise-header__container">
<div className="enterprise-header__left">
<h1>Kapacitor Rules</h1>
</div>
</div>
</div>
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
{this.renderDataSection()}
</div>
</div>
<div className="row">
<div className="col-md-12">
{this.renderValuesSection()}
</div>
</div>
<div className="row">
<div className="col-md-12">
{this.renderMessageSection()}
</div>
</div>
<div className="row">
<div className="col-md-12">
{this.renderAlertsSection()}
</div>
</div>
</div>
</div>
);
},
renderDataSection() {
return (
<div className="kapacitor-rule-section">
<h3>Data</h3>
<DataSection source={this.props.source} />
</div>
);
},
renderValuesSection() {
return (
<div className="kapacitor-rule-section">
<h3>Values</h3>
</div>
);
},
renderMessageSection() {
return (
<div className="kapacitor-rule-section">
<h3>Message</h3>
<textarea />
</div>
);
},
renderAlertsSection() {
// hit kapacitor config endpoint and filter sections by the "enabled" property
const alertOptions = ['Slack', 'VictorOps'].map((destination) => {
return <option key={destination}>send to {destination}</option>;
})
return (
<div className="kapacitor-rule-section">
<h3>Alerts</h3>
<p>The Alert should <select>{alertOptions}</select></p>
</div>
);
},
});
export default KapacitorRulePage;

View File

@ -1,3 +1,4 @@
import KapacitorPage from './containers/KapacitorPage';
import KapacitorRulePage from './containers/KapacitorRulePage';
import KapacitorTasksPage from './containers/KapacitorTasksPage';
export {KapacitorPage, KapacitorTasksPage};
export {KapacitorPage, KapacitorRulePage, KapacitorTasksPage};

View File

@ -41,8 +41,8 @@ const SideNav = React.createClass({
</NavBlock>
<NavBlock matcher="alerts" icon="alert-triangle" link={`${sourcePrefix}/alerts`}>
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerts" />
<NavListItem matcher="view" link={`${sourcePrefix}/alerts`}>View</NavListItem>
<NavListItem matcher="rules" link={`${sourcePrefix}/alert/rules`}>Rules</NavListItem>
<NavListItem link={`${sourcePrefix}/alerts`}>View</NavListItem>
<NavListItem link={`${sourcePrefix}/alert-rules`}>Rules</NavListItem>
</NavBlock>
</NavBar>
);