diff --git a/CHANGELOG.md b/CHANGELOG.md index 310b9179a8..7613754c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ 1. [#1708](https://github.com/influxdata/chronograf/pull/1708): Fix z-index issue in dashboard cell context menu ### Features +1. [#1717](https://github.com/influxdata/chronograf/pull/1717): View server generated TICKscripts 1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards. + ### UI Improvements 1. [#1707](https://github.com/influxdata/chronograf/pull/1707): Polish alerts table in status page to wrap text less diff --git a/ui/.eslintrc b/ui/.eslintrc index 7d3571e8c3..b2aa916c4a 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -220,7 +220,7 @@ 'react/jsx-uses-react': 2, 'react/jsx-uses-vars': 2, 'react/no-danger': 2, - 'react/no-did-mount-set-state': 'error', + 'react/no-did-mount-set-state': 0, 'react/no-did-update-set-state': 2, 'react/no-direct-mutation-state': 2, 'react/no-is-mounted': 2, diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index 8867dd432a..1452c32945 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -7,6 +7,7 @@ import { deleteRule as deleteRuleAPI, updateRuleStatus as updateRuleStatusAPI, } from 'src/kapacitor/apis' +import {errorThrown} from 'shared/actions/errors' export function fetchRule(source, ruleID) { return dispatch => { @@ -48,16 +49,12 @@ export function loadDefaultRule() { } } -export function fetchRules(kapacitor) { - return dispatch => { - getRules(kapacitor).then(({data: {rules}}) => { - dispatch({ - type: 'LOAD_RULES', - payload: { - rules, - }, - }) - }) +export const fetchRules = kapacitor => async dispatch => { + try { + const {data: {rules}} = await getRules(kapacitor) + dispatch({type: 'LOAD_RULES', payload: {rules}}) + } catch (error) { + dispatch(errorThrown(error)) } } diff --git a/ui/src/kapacitor/components/KapacitorRules.js b/ui/src/kapacitor/components/KapacitorRules.js index 5e22ee3f9e..e13b4f6d00 100644 --- a/ui/src/kapacitor/components/KapacitorRules.js +++ b/ui/src/kapacitor/components/KapacitorRules.js @@ -5,6 +5,7 @@ import NoKapacitorError from 'shared/components/NoKapacitorError' import SourceIndicator from 'shared/components/SourceIndicator' import KapacitorRulesTable from 'src/kapacitor/components/KapacitorRulesTable' import FancyScrollbar from 'shared/components/FancyScrollbar' +import TICKscriptOverlay from 'src/kapacitor/components/TICKscriptOverlay' const KapacitorRules = ({ source, @@ -12,8 +13,29 @@ const KapacitorRules = ({ hasKapacitor, loading, onDelete, + tickscript, onChangeRuleStatus, + onReadTickscript, + onCloseTickscript, }) => { + if (loading) { + return ( + +
+

Alert Rules

+ +
+
+
+

Loading Rules...

+
+
+
+ ) + } + if (!hasKapacitor) { return ( @@ -22,18 +44,16 @@ const KapacitorRules = ({ ) } - if (loading) { - return ( - -

Loading...

-
- ) - } const tableHeader = rules.length === 1 ? '1 Alert Rule' : `${rules.length} Alert Rules` return ( - +

{tableHeader}

) } -const PageContents = ({children, source}) => +const PageContents = ({children, source, tickscript, onCloseTickscript}) => (
@@ -76,9 +97,16 @@ const PageContents = ({children, source}) =>
+ {tickscript + ? + : null}
+) -const {arrayOf, bool, func, shape, node} = PropTypes +const {arrayOf, bool, func, node, shape, string} = PropTypes KapacitorRules.propTypes = { source: shape(), @@ -87,11 +115,16 @@ KapacitorRules.propTypes = { loading: bool, onChangeRuleStatus: func, onDelete: func, + tickscript: string, + onReadTickscript: func, + onCloseTickscript: func, } PageContents.propTypes = { children: node, source: shape(), + tickscript: string, + onCloseTickscript: func, } export default KapacitorRules diff --git a/ui/src/kapacitor/components/KapacitorRulesTable.js b/ui/src/kapacitor/components/KapacitorRulesTable.js index b405a32ce1..87587b914a 100644 --- a/ui/src/kapacitor/components/KapacitorRulesTable.js +++ b/ui/src/kapacitor/components/KapacitorRulesTable.js @@ -1,21 +1,38 @@ import React, {PropTypes} from 'react' import {Link} from 'react-router' +import _ from 'lodash' -const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) => +import {KAPACITOR_RULES_TABLE} from 'src/kapacitor/constants/tableSizing' +const { + colName, + colType, + colMessage, + colAlerts, + colEnabled, + colActions, +} = KAPACITOR_RULES_TABLE + +const KapacitorRulesTable = ({ + rules, + source, + onDelete, + onReadTickscript, + onChangeRuleStatus, +}) =>
- - - - - - + + + + + - {rules.map(rule => { + {_.sortBy(rules, r => r.name.toLowerCase()).map(rule => { return ( source={source} onDelete={onDelete} onChangeRuleStatus={onChangeRuleStatus} + onRead={onReadTickscript} /> ) })} @@ -30,15 +48,24 @@ const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>
NameRule TypeMessageAlertsEnabled + NameRule TypeMessageAlertsEnabled
-const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) => +const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) => - + - {rule.trigger} - {rule.message} - {rule.alerts.join(', ')} - + {rule.trigger} + + + {rule.message} + + + + {rule.alerts.join(', ')} + +
- + + @@ -75,6 +105,7 @@ KapacitorRulesTable.propTypes = { source: shape({ id: string.isRequired, }).isRequired, + onReadTickscript: func, } RuleRow.propTypes = { @@ -82,6 +113,7 @@ RuleRow.propTypes = { source: shape(), onChangeRuleStatus: func, onDelete: func, + onRead: func, } RuleTitle.propTypes = { @@ -90,7 +122,7 @@ RuleTitle.propTypes = { query: shape(), links: shape({ self: string.isRequired, - }).isRequired, + }), }), source: shape({ id: string.isRequired, diff --git a/ui/src/kapacitor/components/TICKscriptOverlay.js b/ui/src/kapacitor/components/TICKscriptOverlay.js new file mode 100644 index 0000000000..87762ae8d1 --- /dev/null +++ b/ui/src/kapacitor/components/TICKscriptOverlay.js @@ -0,0 +1,30 @@ +import React, {PropTypes} from 'react' +import OverlayTechnologies from 'shared/components/OverlayTechnologies' + +const TICKscriptOverlay = ({tickscript, onClose}) => + +
+
+
+

Generated TICKscript

+
+
+ +
+
+
+
+          {tickscript}
+        
+
+
+
+ +const {string, func} = PropTypes + +TICKscriptOverlay.propTypes = { + tickscript: string, + onClose: func.isRequired, +} + +export default TICKscriptOverlay diff --git a/ui/src/kapacitor/constants/tableSizing.js b/ui/src/kapacitor/constants/tableSizing.js new file mode 100644 index 0000000000..5d96be7625 --- /dev/null +++ b/ui/src/kapacitor/constants/tableSizing.js @@ -0,0 +1,8 @@ +export const KAPACITOR_RULES_TABLE = { + colName: '200px', + colType: '90px', + colMessage: '460px', + colAlerts: '120px', + colEnabled: '64px', + colActions: '176px', +} diff --git a/ui/src/kapacitor/containers/KapacitorRulePage.js b/ui/src/kapacitor/containers/KapacitorRulePage.js index c1d959d854..7056df5c24 100644 --- a/ui/src/kapacitor/containers/KapacitorRulePage.js +++ b/ui/src/kapacitor/containers/KapacitorRulePage.js @@ -1,4 +1,4 @@ -import React, {PropTypes} from 'react' +import React, {PropTypes, Component} from 'react' import {connect} from 'react-redux' import _ from 'lodash' @@ -10,45 +10,19 @@ import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index' import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants' import KapacitorRule from 'src/kapacitor/components/KapacitorRule' -export const KapacitorRulePage = React.createClass({ - propTypes: { - source: PropTypes.shape({ - links: PropTypes.shape({ - proxy: PropTypes.string.isRequired, - self: PropTypes.string.isRequired, - }), - }), - addFlashMessage: PropTypes.func, - rules: PropTypes.shape({}).isRequired, - queryConfigs: PropTypes.shape({}).isRequired, - kapacitorActions: PropTypes.shape({ - loadDefaultRule: PropTypes.func.isRequired, - fetchRule: PropTypes.func.isRequired, - chooseTrigger: PropTypes.func.isRequired, - addEvery: PropTypes.func.isRequired, - removeEvery: PropTypes.func.isRequired, - updateRuleValues: PropTypes.func.isRequired, - updateMessage: PropTypes.func.isRequired, - updateAlerts: PropTypes.func.isRequired, - updateRuleName: PropTypes.func.isRequired, - }).isRequired, - queryActions: PropTypes.shape({}).isRequired, - params: PropTypes.shape({ - ruleID: PropTypes.string, - }).isRequired, - router: PropTypes.shape({ - push: PropTypes.func.isRequired, - }).isRequired, - }, +class KapacitorRulePage extends Component { + constructor(props) { + super(props) - getInitialState() { - return { + this.state = { enabledAlerts: [], kapacitor: {}, } - }, - componentDidMount() { + this.isEditing = ::this.isEditing + } + + async componentDidMount() { const {params, source, kapacitorActions, addFlashMessage} = this.props if (this.isEditing()) { kapacitorActions.fetchRule(source, params.ruleID) @@ -56,35 +30,36 @@ export const KapacitorRulePage = React.createClass({ kapacitorActions.loadDefaultRule() } - getActiveKapacitor(source).then(kapacitor => { - this.setState({kapacitor}) - getKapacitorConfig(kapacitor) - .then(({data: {sections}}) => { - const enabledAlerts = Object.keys(sections).filter(section => { - return ( - _.get( - sections, - [section, 'elements', '0', 'options', 'enabled'], - false - ) && ALERTS.includes(section) - ) - }) - this.setState({enabledAlerts}) - }) - .catch(() => { - addFlashMessage({ - type: 'error', - text: 'There was a problem communicating with Kapacitor', - }) - }) - .catch(() => { - addFlashMessage({ - type: 'error', - text: "We couldn't find a configured Kapacitor for this source", // eslint-disable-line quotes - }) - }) - }) - }, + const kapacitor = await getActiveKapacitor(this.props.source) + if (!kapacitor) { + return addFlashMessage({ + type: 'error', + text: "We couldn't find a configured Kapacitor for this source", // eslint-disable-line quotes + }) + } + + try { + const {data: {sections}} = await getKapacitorConfig(kapacitor) + const enabledAlerts = Object.keys(sections).filter(section => { + return ( + _.get( + sections, + [section, 'elements', '0', 'options', 'enabled'], + false + ) && ALERTS.includes(section) + ) + }) + + this.setState({kapacitor, enabledAlerts}) + } catch (error) { + addFlashMessage({ + type: 'error', + text: 'There was a problem communicating with Kapacitor', + }) + console.error(error) + throw error + } + } render() { const { @@ -123,26 +98,54 @@ export const KapacitorRulePage = React.createClass({ kapacitor={kapacitor} /> ) - }, + } isEditing() { const {params} = this.props return params.ruleID && params.ruleID !== 'new' - }, + } +} + +const {func, shape, string} = PropTypes + +KapacitorRulePage.propTypes = { + source: shape({ + links: shape({ + proxy: string.isRequired, + self: string.isRequired, + }), + }), + addFlashMessage: func, + rules: shape({}).isRequired, + queryConfigs: shape({}).isRequired, + kapacitorActions: shape({ + loadDefaultRule: func.isRequired, + fetchRule: func.isRequired, + chooseTrigger: func.isRequired, + addEvery: func.isRequired, + removeEvery: func.isRequired, + updateRuleValues: func.isRequired, + updateMessage: func.isRequired, + updateAlerts: func.isRequired, + updateRuleName: func.isRequired, + }).isRequired, + queryActions: shape({}).isRequired, + params: shape({ + ruleID: string, + }).isRequired, + router: shape({ + push: func.isRequired, + }).isRequired, +} + +const mapStateToProps = ({rules, queryConfigs}) => ({ + rules, + queryConfigs, }) -function mapStateToProps(state) { - return { - rules: state.rules, - queryConfigs: state.queryConfigs, - } -} - -function mapDispatchToProps(dispatch) { - return { - kapacitorActions: bindActionCreators(kapacitorActionCreators, dispatch), - queryActions: bindActionCreators(queryActionCreators, dispatch), - } -} +const mapDispatchToProps = dispatch => ({ + kapacitorActions: bindActionCreators(kapacitorActionCreators, dispatch), + queryActions: bindActionCreators(queryActionCreators, dispatch), +}) export default connect(mapStateToProps, mapDispatchToProps)(KapacitorRulePage) diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index 90b11e25ba..653f1160e2 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -11,19 +11,23 @@ class KapacitorRulesPage extends Component { this.state = { hasKapacitor: false, loading: true, + tickscript: null, } this.handleDeleteRule = ::this.handleDeleteRule this.handleRuleStatus = ::this.handleRuleStatus + this.handleReadTickscript = ::this.handleReadTickscript + this.handleCloseTickscript = ::this.handleCloseTickscript } - componentDidMount() { - getActiveKapacitor(this.props.source).then(kapacitor => { - if (kapacitor) { - this.props.actions.fetchRules(kapacitor) - } - this.setState({loading: false, hasKapacitor: !!kapacitor}) - }) + async componentDidMount() { + const kapacitor = await getActiveKapacitor(this.props.source) + if (!kapacitor) { + return + } + + await this.props.actions.fetchRules(kapacitor) + this.setState({loading: false, hasKapacitor: !!kapacitor}) } handleDeleteRule(rule) { @@ -39,9 +43,17 @@ class KapacitorRulesPage extends Component { actions.updateRuleStatusSuccess(rule.id, status) } + handleReadTickscript({tickscript}) { + this.setState({tickscript}) + } + + handleCloseTickscript() { + this.setState({tickscript: null}) + } + render() { const {source, rules} = this.props - const {hasKapacitor, loading} = this.state + const {hasKapacitor, loading, tickscript} = this.state return ( ) } diff --git a/ui/src/shared/apis/index.js b/ui/src/shared/apis/index.js index 2664cb725f..6ec40d6e25 100644 --- a/ui/src/shared/apis/index.js +++ b/ui/src/shared/apis/index.js @@ -67,14 +67,19 @@ export function getKapacitor(source, kapacitorID) { }) } -export function getActiveKapacitor(source) { - return AJAX({ - url: source.links.kapacitors, - method: 'GET', - }).then(({data}) => { +export const getActiveKapacitor = async source => { + try { + const {data} = await AJAX({ + url: source.links.kapacitors, + method: 'GET', + }) + const activeKapacitor = data.kapacitors.find(k => k.active) return activeKapacitor || data.kapacitors[0] - }) + } catch (error) { + console.error(error) + throw error + } } export const getKapacitors = async source => { @@ -138,11 +143,11 @@ export function updateKapacitor({ }) } -export function getKapacitorConfig(kapacitor) { - return kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/config', '') +export const getKapacitorConfig = async kapacitor => { + return await kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/config', '') } -export function getKapacitorConfigSection(kapacitor, section) { +export const getKapacitorConfigSection = (kapacitor, section) => { return kapacitorProxy(kapacitor, 'GET', `/kapacitor/v1/config/${section}`, '') } @@ -163,11 +168,9 @@ export function updateKapacitorConfigSection(kapacitor, section, properties) { } export function testAlertOutput(kapacitor, outputName, properties) { - return kapacitorProxy( - kapacitor, - 'GET', - '/kapacitor/v1/service-tests' - ).then(({data: {services}}) => { + return kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests').then(({ + data: {services}, + }) => { const service = services.find(s => s.name === outputName) return kapacitorProxy( kapacitor, diff --git a/ui/src/style/components/tables.scss b/ui/src/style/components/tables.scss index 7f385f4a91..632f17e193 100644 --- a/ui/src/style/components/tables.scss +++ b/ui/src/style/components/tables.scss @@ -261,3 +261,14 @@ $table-tab-scrollbar-height: 6px; text-overflow: ellipsis; } } + +/* + No Wrap Cells + ---------------------------------------------- +*/ + +table .table-cell-nowrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/ui/src/style/unsorted.scss b/ui/src/style/unsorted.scss index bba610cb2f..6e6c9c9d21 100644 --- a/ui/src/style/unsorted.scss +++ b/ui/src/style/unsorted.scss @@ -217,3 +217,30 @@ br { color: $g11-sidewalk; font-size: 13px; } + + + +/* + View TICKscript Overlay + ----------------------------------------------------------------------------- +*/ +$tick-script-overlay-margin: 30px; +.tick-script-overlay { + max-width: 960px; + margin: 0 auto $tick-script-overlay-margin auto; + height: calc(100% - #{$tick-script-overlay-margin}); + position: relative; + + .write-data-form--body { + height: calc(100% - 60px); + display: flex; + flex-direction: column; + } +} +.tick-script-overlay--sample { + margin: 0; + white-space: pre-wrap; + font-size: 14px; + padding: 20px; + border: 2px solid $g4-onyx; +}