diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d3b0ffc4..bd0ee92418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,17 @@ ### Bug Fixes 1. [#1337](https://github.com/influxdata/chronograf/pull/1337): Fix no apps for hosts false negative + 1. [#1340](https://github.com/influxdata/chronograf/pull/1340): Fix no active query in DE and Cell editing + 1. [#1338](https://github.com/influxdata/chronograf/pull/1338): Require url and name when adding a new source + 1. [#1348](https://github.com/influxdata/chronograf/pull/1348): Fix broken 'Add Kapacitor' Link ### Features ### UI Improvements 1. [#1335](https://github.com/influxdata/chronograf/pull/1335): Improve UX for sanitized kapacitor settings 1. [#1342](https://github.com/influxdata/chronograf/pull/1342): No more sort-as-you-type in DB admin + 1. [#1344](https://github.com/influxdata/chronograf/pull/1344): Remove K8 dashboard + 1. [#1340](https://github.com/influxdata/chronograf/pull/1340): Automatically switch to table view if meta query ## v1.2.0-beta9 [2017-04-21] @@ -17,6 +22,8 @@ 1. [#1269](https://github.com/influxdata/chronograf/issues/1269): Add more functionality to the explorer's query generation process 1. [#1318](https://github.com/influxdata/chronograf/issues/1318): Fix JWT refresh for auth-durations of zero and less than five minutes 1. [#1332](https://github.com/influxdata/chronograf/pull/1332): Remove table toggle from dashboard visualization + 1. [#1335](https://github.com/influxdata/chronograf/pull/1335): Improve UX for sanitized kapacitor settings + ### Features 1. [#1232](https://github.com/influxdata/chronograf/pull/1232): Fuse the query builder and raw query editor diff --git a/ui/src/dashboards/components/DashboardHeader.js b/ui/src/dashboards/components/DashboardHeader.js index 0ce37f6411..4ab53fab64 100644 --- a/ui/src/dashboards/components/DashboardHeader.js +++ b/ui/src/dashboards/components/DashboardHeader.js @@ -32,11 +32,8 @@ const DashboardHeader = ({ - - } - {headerText && -

Kubernetes Dashboard

- } + } + {headerText}
diff --git a/ui/src/dashboards/containers/DashboardsPage.js b/ui/src/dashboards/containers/DashboardsPage.js index 4b2ba2f830..99f6b5fd23 100644 --- a/ui/src/dashboards/containers/DashboardsPage.js +++ b/ui/src/dashboards/containers/DashboardsPage.js @@ -11,12 +11,7 @@ import {getDashboardsAsync, deleteDashboardAsync} from 'src/dashboards/actions' import {NEW_DASHBOARD} from 'src/dashboards/constants' -const { - arrayOf, - func, - string, - shape, -} = PropTypes +const {arrayOf, func, string, shape} = PropTypes const DashboardsPage = React.createClass({ propTypes: { @@ -57,10 +52,10 @@ const DashboardsPage = React.createClass({ let tableHeader if (dashboards === null) { tableHeader = 'Loading Dashboards...' - } else if (dashboards.length === 0) { + } else if (dashboards.length === 1) { tableHeader = '1 Dashboard' } else { - tableHeader = `${dashboards.length + 1} Dashboards` + tableHeader = `${dashboards.length} Dashboards` } return ( @@ -84,43 +79,52 @@ const DashboardsPage = React.createClass({

{tableHeader}

- +
- - - - - - - - - { - dashboards && dashboards.length ? - dashboards.map((dashboard) => { - return ( - - - - - ) - }) : - null - } - - - - - -
Name
- - {dashboard.name} - -
- - {'Kubernetes'} - -
+ {dashboards && dashboards.length + ? + + + + + + + {dashboards.map(dashboard => ( + + + + + ))} + +
Name +
+ + {dashboard.name} + +
+ :
+

+ Looks like you dont have any dashboards +

+ +
}
@@ -137,9 +141,11 @@ const mapStateToProps = ({dashboardUI: {dashboards, dashboard}}) => ({ dashboard, }) -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = dispatch => ({ handleGetDashboards: bindActionCreators(getDashboardsAsync, dispatch), handleDeleteDashboard: bindActionCreators(deleteDashboardAsync, dispatch), }) -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(DashboardsPage)) +export default connect(mapStateToProps, mapDispatchToProps)( + withRouter(DashboardsPage) +) diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 40c3d2789e..410ca2fc98 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -4,8 +4,10 @@ import classNames from 'classnames' import VisHeader from 'src/data_explorer/components/VisHeader' import VisView from 'src/data_explorer/components/VisView' import {GRAPH, TABLE} from 'src/shared/constants' +import _ from 'lodash' const {arrayOf, func, number, shape, string} = PropTypes +const META_QUERY_REGEX = /^show/i const Visualization = React.createClass({ propTypes: { @@ -33,18 +35,12 @@ const Visualization = React.createClass({ }, getInitialState() { - const {queryConfigs, activeQueryIndex} = this.props - if (!queryConfigs.length || activeQueryIndex === null) { - return { - view: GRAPH, - } - } + const {activeQueryIndex, queryConfigs} = this.props + const activeQueryText = this.getQueryText(queryConfigs, activeQueryIndex) - return { - view: typeof queryConfigs[activeQueryIndex].rawText === 'string' - ? TABLE - : GRAPH, - } + return activeQueryText.match(META_QUERY_REGEX) + ? {view: TABLE} + : {view: GRAPH} }, getDefaultProps() { @@ -54,19 +50,22 @@ const Visualization = React.createClass({ }, componentWillReceiveProps(nextProps) { - const {queryConfigs, activeQueryIndex} = nextProps - if ( - !queryConfigs.length || - activeQueryIndex === null || - activeQueryIndex === this.props.activeQueryIndex - ) { + const {activeQueryIndex, queryConfigs} = nextProps + const nextQueryText = this.getQueryText(queryConfigs, activeQueryIndex) + const queryText = this.getQueryText( + this.props.queryConfigs, + this.props.activeQueryIndex + ) + + if (queryText === nextQueryText) { return } - const activeQuery = queryConfigs[activeQueryIndex] - if (activeQuery && typeof activeQuery.rawText === 'string') { + if (nextQueryText.match(META_QUERY_REGEX)) { return this.setState({view: TABLE}) } + + this.setState({view: GRAPH}) }, handleToggleView(view) { @@ -125,6 +124,11 @@ const Visualization = React.createClass({ ) }, + + getQueryText(queryConfigs, index) { + // rawText can be null + return _.get(queryConfigs, [`${index}`, 'rawText'], '') || '' + }, }) export default Visualization diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 7d8d81e427..33f60d720b 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -57,7 +57,7 @@ const DataExplorer = React.createClass({ getInitialState() { return { - activeQueryIndex: null, + activeQueryIndex: 0, } }, diff --git a/ui/src/index.js b/ui/src/index.js index 119223a980..c93fd0880a 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -9,7 +9,6 @@ import App from 'src/App' import AlertsApp from 'src/alerts' import CheckSources from 'src/CheckSources' import {HostsPage, HostPage} from 'src/hosts' -import {KubernetesPage} from 'src/kubernetes' import {Login, UserIsAuthenticated, UserIsNotAuthenticated} from 'src/auth' import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor' import DataExplorer from 'src/data_explorer' @@ -108,7 +107,6 @@ const Root = React.createClass({ - diff --git a/ui/src/kubernetes/components/KubernetesDashboard.js b/ui/src/kubernetes/components/KubernetesDashboard.js deleted file mode 100644 index dac4b6ada3..0000000000 --- a/ui/src/kubernetes/components/KubernetesDashboard.js +++ /dev/null @@ -1,107 +0,0 @@ -import React, {PropTypes} from 'react' -import classnames from 'classnames' - -import LayoutRenderer from 'shared/components/LayoutRenderer' -import DashboardHeader from 'src/dashboards/components/DashboardHeader' -import timeRanges from 'hson!../../shared/data/timeRanges.hson' - -const { - arrayOf, - bool, - func, - number, - shape, - string, -} = PropTypes - -export const KubernetesDashboard = React.createClass({ - propTypes: { - source: shape({ - links: shape({ - proxy: string.isRequired, - }).isRequired, - telegraf: string.isRequired, - }), - layouts: arrayOf(shape().isRequired).isRequired, - autoRefresh: number.isRequired, - handleChooseAutoRefresh: func.isRequired, - inPresentationMode: bool.isRequired, - handleClickPresentationButton: func, - }, - - getInitialState() { - const fifteenMinutesIndex = 1 - return { - timeRange: timeRanges[fifteenMinutesIndex], - } - }, - - renderLayouts(layouts) { - const {timeRange} = this.state - const {source, autoRefresh} = this.props - - let layoutCells = [] - layouts.forEach((layout) => { - layoutCells = layoutCells.concat(layout.cells) - }) - - layoutCells.forEach((cell, i) => { - cell.queries.forEach((q) => { - q.text = q.query - q.database = source.telegraf - }) - cell.x = (i * 4 % 12) // eslint-disable-line no-magic-numbers - cell.y = 0 - }) - - return ( - - ) - }, - - handleChooseTimeRange({lower}) { - const timeRange = timeRanges.find((range) => range.lower === lower) - this.setState({timeRange}) - }, - - render() { - const {layouts, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton, source} = this.props - const {timeRange} = this.state - const emptyState = ( -
- -

No Kubernetes configuration found

-
- ) - - return ( -
- -
-
- {layouts.length ? this.renderLayouts(layouts) : emptyState} -
-
-
- ) - }, -}) - -export default KubernetesDashboard diff --git a/ui/src/kubernetes/containers/KubernetesPage.js b/ui/src/kubernetes/containers/KubernetesPage.js deleted file mode 100644 index de342fa2d3..0000000000 --- a/ui/src/kubernetes/containers/KubernetesPage.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, {PropTypes} from 'react' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import {fetchLayouts} from 'shared/apis' -import KubernetesDashboard from 'src/kubernetes/components/KubernetesDashboard' - -import {setAutoRefresh} from 'shared/actions/app' -import {presentationButtonDispatcher} from 'shared/dispatchers' - -const { - bool, - func, - number, - shape, - string, -} = PropTypes - -export const KubernetesPage = React.createClass({ - propTypes: { - source: shape({ - links: shape({ - proxy: string.isRequired, - }).isRequired, - }), - autoRefresh: number.isRequired, - handleChooseAutoRefresh: func.isRequired, - inPresentationMode: bool.isRequired, - handleClickPresentationButton: func, - }, - - getInitialState() { - return { - layouts: [], - } - }, - - componentDidMount() { - fetchLayouts().then(({data: {layouts}}) => { - const kubernetesLayouts = layouts.filter((l) => l.app === 'kubernetes') - this.setState({layouts: kubernetesLayouts}) - }) - }, - - render() { - const {layouts} = this.state - const {source, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton} = this.props - - return ( - - ) - }, -}) - -const mapStateToProps = ({app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}}) => ({ - inPresentationMode, - autoRefresh, -}) - -const mapDispatchToProps = (dispatch) => ({ - handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch), - handleClickPresentationButton: presentationButtonDispatcher(dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(KubernetesPage) diff --git a/ui/src/kubernetes/index.js b/ui/src/kubernetes/index.js deleted file mode 100644 index 891c2af4d0..0000000000 --- a/ui/src/kubernetes/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import KubernetesPage from './containers/KubernetesPage' -export {KubernetesPage} diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 62d76482a4..1721c3a18b 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -94,7 +94,7 @@ export const LayoutRenderer = React.createClass({ return cells.map((cell) => { const qs = cell.queries.map((query) => { - // TODO: Canned dashboards (and possibly Kubernetes dashboard) use an old query schema, + // TODO: Canned dashboards use an old query schema, // which does not have enough information for the new `buildInfluxQLQuery` function // to operate on. We will use `buildQueryForOldQuerySchema` until we conform // on a stable query representation. diff --git a/ui/src/shared/components/NoKapacitorError.js b/ui/src/shared/components/NoKapacitorError.js index fda9ce61f8..530a90bead 100644 --- a/ui/src/shared/components/NoKapacitorError.js +++ b/ui/src/shared/components/NoKapacitorError.js @@ -9,7 +9,7 @@ const NoKapacitorError = React.createClass({ }, render() { - const path = `/sources/${this.props.source.id}/kapacitor-config` + const path = `/sources/${this.props.source.id}/kapacitors/new` return (

The current source does not have an associated Kapacitor instance, please configure one.

diff --git a/ui/src/sources/components/SourceForm.js b/ui/src/sources/components/SourceForm.js index 4dd228a7f2..a5813f3213 100644 --- a/ui/src/sources/components/SourceForm.js +++ b/ui/src/sources/components/SourceForm.js @@ -3,11 +3,7 @@ import classNames from 'classnames' import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText' import _ from 'lodash' -const { - bool, - func, - shape, -} = PropTypes +const {bool, func, shape} = PropTypes export const SourceForm = React.createClass({ propTypes: { @@ -28,7 +24,9 @@ export const SourceForm = React.createClass({ password: this.sourcePassword.value, 'default': this.sourceDefault.checked, telegraf: this.sourceTelegraf.value, - insecureSkipVerify: this.sourceInsecureSkipVerify ? this.sourceInsecureSkipVerify.checked : false, + insecureSkipVerify: this.sourceInsecureSkipVerify + ? this.sourceInsecureSkipVerify.checked + : false, metaUrl: this.metaUrl && this.metaUrl.value.trim(), } @@ -56,50 +54,126 @@ export const SourceForm = React.createClass({ return (

Connection Details

-
+
- this.sourceURL = r} className="form-control" id="connect-string" placeholder="http://localhost:8086" onChange={onInputChange} value={source.url || ''} onBlur={this.handleBlurSourceURL}> + this.sourceURL = r} + className="form-control" + id="connect-string" + placeholder="http://localhost:8086" + onChange={onInputChange} + value={source.url || ''} + onBlur={this.handleBlurSourceURL} + required={true} + />
- this.sourceName = r} className="form-control" id="name" placeholder="Influx 1" onChange={onInputChange} value={source.name || ''}> + this.sourceName = r} + className="form-control" + id="name" + placeholder="Influx 1" + onChange={onInputChange} + value={source.name || ''} + required={true} + />
- this.sourceUsername = r} className="form-control" id="username" onChange={onInputChange} value={source.username || ''}> + this.sourceUsername = r} + className="form-control" + id="username" + onChange={onInputChange} + value={source.username || ''} + />
- this.sourcePassword = r} className="form-control" id="password" onChange={onInputChange} value={source.password || ''}> + this.sourcePassword = r} + className="form-control" + id="password" + onChange={onInputChange} + value={source.password || ''} + />
- {_.get(source, 'type', '').includes('enterprise') ? -
+ {_.get(source, 'type', '').includes('enterprise') + ?
- this.metaUrl = r} className="form-control" id="meta-url" placeholder="http://localhost:8091" onChange={onInputChange} value={source.metaUrl || ''}> -
: null} + this.metaUrl = r} + className="form-control" + id="meta-url" + placeholder="http://localhost:8091" + onChange={onInputChange} + value={source.metaUrl || ''} + /> +
+ : null}
- this.sourceTelegraf = r} className="form-control" id="telegraf" onChange={onInputChange} value={source.telegraf || 'telegraf'}> + this.sourceTelegraf = r} + className="form-control" + id="telegraf" + onChange={onInputChange} + value={source.telegraf || 'telegraf'} + />
- this.sourceDefault = r} /> - + this.sourceDefault = r} + /> +
- {_.get(source, 'url', '').startsWith('https') ? -
-
- this.sourceInsecureSkipVerify = r} /> - + {_.get(source, 'url', '').startsWith('https') + ?
+
+ this.sourceInsecureSkipVerify = r} + /> + +
+
- -
: null} + : null}
- +