From 6bd85d4601e4d87bd845ac1d80e28d6a27792d9c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 11:19:33 -0800 Subject: [PATCH 01/30] Start reorganization of DE --- .../data_explorer/components/QueryBuilder.js | 160 ++++++++++++++++++ .../data_explorer/components/Visualization.js | 13 +- .../data_explorer/containers/DataExplorer.js | 37 ++-- 3 files changed, 181 insertions(+), 29 deletions(-) create mode 100644 ui/src/data_explorer/components/QueryBuilder.js diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js new file mode 100644 index 000000000..c87116ce0 --- /dev/null +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -0,0 +1,160 @@ +import React, {PropTypes} from 'react'; +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import QueryEditor from './QueryEditor'; +import QueryTabItem from './QueryTabItem'; +import SimpleDropdown from 'src/shared/components/SimpleDropdown'; + +import * as viewActions from '../actions/view'; + +const QueryBuilder = React.createClass({ + propTypes: { + queries: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + timeRange: PropTypes.shape({ + upper: PropTypes.string, + lower: PropTypes.string, + }).isRequired, + actions: PropTypes.shape({ + chooseNamespace: PropTypes.func.isRequired, + chooseMeasurement: PropTypes.func.isRequired, + chooseTag: PropTypes.func.isRequired, + groupByTag: PropTypes.func.isRequired, + addQuery: PropTypes.func.isRequired, + deleteQuery: PropTypes.func.isRequired, + toggleField: PropTypes.func.isRequired, + groupByTime: PropTypes.func.isRequired, + toggleTagAcceptance: PropTypes.func.isRequired, + applyFuncsToField: PropTypes.func.isRequired, + deletePanel: PropTypes.func.isRequired, + renamePanel: PropTypes.func.isRequired, + }).isRequired, + setActiveQuery: PropTypes.func.isRequired, + activeQueryID: PropTypes.string, + }, + + handleSetActiveQuery(query) { + this.props.setActiveQuery(query.id); + }, + + handleAddQuery() { + this.props.actions.addQuery(); + }, + + handleAddRawQuery() { + this.props.actions.addQuery({rawText: `SELECT "fields" from "db"."rp"."measurement"`}); + }, + + handleDeleteQuery(query) { + this.props.actions.deleteQuery(query.id); + }, + + getActiveQuery() { + const {queries, activeQueryID} = this.props; + const activeQuery = queries.find((query) => query.id === activeQueryID); + const defaultQuery = queries[0]; + + return activeQuery || defaultQuery; + }, + + render() { + return ( +
+
+
+ + {"Graph"} +
+
+ {/*
*/} +
+
+ {this.renderQueryTabList()} + {this.renderQueryEditor()} +
+ ); + }, + + renderQueryEditor() { + const {timeRange, actions} = this.props; + const query = this.getActiveQuery(); + + if (!query) { + return ( +
+
This Graph has no Queries
+
+
Add a Query
+
+ ); + } + + return ( + + ); + }, + + renderQueryTabList() { + const {queries} = this.props; + return ( +
+ {queries.map((q) => { + let queryTabText; + if (q.rawText) { + queryTabText = 'InfluxQL'; + } else { + queryTabText = (q.measurement && q.fields.length !== 0) ? `${q.measurement}.${q.fields[0].field}` : 'Query'; + } + return ( + + ); + })} + + {this.renderAddQuery()} +
+ ); + }, + + onChoose(item) { + switch (item.text) { + case 'Query Builder': + this.handleAddQuery(); + break; + case 'InfluxQL': + this.handleAddRawQuery(); + break; + } + }, + + renderAddQuery() { + return ( + + + + ); + }, +}); + +function mapStateToProps() { + return {}; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators(viewActions, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(QueryBuilder); diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 027a171c1..2a0705b70 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -13,7 +13,6 @@ const Visualization = React.createClass({ lower: PropTypes.string, }).isRequired, queryConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - isActive: PropTypes.bool.isRequired, name: PropTypes.string, activeQueryIndex: PropTypes.number, }, @@ -32,20 +31,12 @@ const Visualization = React.createClass({ }; }, - componentDidUpdate() { - if (this.props.isActive) { - this.panel.scrollIntoView(); - // scrollIntoView scrolls slightly *too* far, so this adds some top offset. - this.panel.parentNode.scrollTop -= 10; - } - }, - handleToggleView() { this.setState({isGraphInView: !this.state.isGraphInView}); }, render() { - const {queryConfigs, timeRange, isActive, name, activeQueryIndex} = this.props; + const {queryConfigs, timeRange, activeQueryIndex} = this.props; const {source} = this.context; const proxyLink = source.links.proxy; @@ -61,7 +52,7 @@ const Visualization = React.createClass({ const isInDataExplorer = true; return ( -
this.panel = p} className={classNames("graph", {active: isActive})}> +
this.panel = p} className={classNames("graph", {active: true})}>
{name || "Graph"} diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 2c7d37f4c..516dd53b0 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -1,9 +1,8 @@ import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; -import PanelBuilder from '../components/PanelBuilder'; -import Visualizations from '../components/Visualizations'; +import QueryBuilder from '../components/QueryBuilder'; +import Visualization from '../components/Visualization'; import Header from '../containers/Header'; -import ResizeContainer from 'shared/components/ResizeContainer'; import { setTimeRange as setTimeRangeAction, @@ -23,6 +22,7 @@ const DataExplorer = React.createClass({ self: string.isRequired, }).isRequired, }).isRequired, + queryConfigs: PropTypes.shape({}), timeRange: shape({ upper: string, lower: string, @@ -55,7 +55,8 @@ const DataExplorer = React.createClass({ }, render() { - const {timeRange, setTimeRange, activePanel} = this.props; + const {timeRange, setTimeRange, activePanel, queryConfigs} = this.props; + const queries = Object.keys(queryConfigs).map((q) => queryConfigs[q]); return (
@@ -63,29 +64,29 @@ const DataExplorer = React.createClass({ actions={{setTimeRange}} timeRange={timeRange} /> - - - - + +
); }, }); function mapStateToProps(state) { - const {timeRange, dataExplorerUI} = state; + const {timeRange, queryConfigs, dataExplorerUI} = state; return { timeRange, + queryConfigs, activePanel: dataExplorerUI.activePanel, }; } From 9e23db06a5a9f79a0be8ee526261e6d67c64a5e9 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 13:35:21 -0800 Subject: [PATCH 02/30] Change style --- ui/src/style/modules/variables.scss | 2 +- ui/src/style/pages/data-explorer.scss | 7 +- .../{panel.scss => query-builder.scss} | 153 +++++++----------- .../pages/data-explorer/visualization.scss | 12 +- 4 files changed, 66 insertions(+), 108 deletions(-) rename ui/src/style/pages/data-explorer/{panel.scss => query-builder.scss} (56%) diff --git a/ui/src/style/modules/variables.scss b/ui/src/style/modules/variables.scss index 352528d92..a8ecc3fae 100644 --- a/ui/src/style/modules/variables.scss +++ b/ui/src/style/modules/variables.scss @@ -11,4 +11,4 @@ $chronograf-page-header-height: 60px; $sidebar-tier1-height: 56px; // Data Explorer -$explorer-page-padding: 18px; +$explorer-page-padding: $page-wrapper-padding; diff --git a/ui/src/style/pages/data-explorer.scss b/ui/src/style/pages/data-explorer.scss index 918bb23cb..743af7d3b 100644 --- a/ui/src/style/pages/data-explorer.scss +++ b/ui/src/style/pages/data-explorer.scss @@ -15,18 +15,21 @@ .page-header { padding-left: $explorer-page-padding; - padding-right: ($explorer-page-padding + $scrollbar-width); + padding-right: $explorer-page-padding; } .page-header__container { max-width: 100%; } + .page-contents { + overflow: hidden; + } } // DE Specific components +@import 'data-explorer/query-builder'; @import 'data-explorer/page-header'; @import 'data-explorer/panel-builder'; -@import 'data-explorer/panel'; @import 'data-explorer/query-editor'; @import 'data-explorer/raw-text'; @import 'data-explorer/tag-list'; diff --git a/ui/src/style/pages/data-explorer/panel.scss b/ui/src/style/pages/data-explorer/query-builder.scss similarity index 56% rename from ui/src/style/pages/data-explorer/panel.scss rename to ui/src/style/pages/data-explorer/query-builder.scss index 0e318e4aa..5449ebce2 100644 --- a/ui/src/style/pages/data-explorer/panel.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -1,101 +1,28 @@ -.panels { - padding: $explorer-page-padding; - overflow: auto; - user-select: none; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - width: 100%; - @include gradient-v($g2-kevlar,$g0-obsidian); - @include custom-scrollbar($g2-kevlar,$c-pool); -} - -.panel { - display: block; +.query-builder { + position: absolute; + width: calc(100% - #{($explorer-page-padding * 2)}); + left: $explorer-page-padding; + height: calc(40% - 16px); + top: 60%; background-color: $g3-castle; border-radius: $radius; - margin-bottom: 6px; - transition: background-color 0.25s ease; border: 0; - - &:hover { - background-color: $g4-onyx; - } - - // For when an panel item is open - &.active { - background-color: $g4-onyx; - - .panel--name { - color: $g20-white; - - .icon { - transform: translateY(-50%) rotate(90deg); - } - } - } -} -// panel Header Bar -.panel--header { - align-items: center; - text-align: center; display: flex; - height: 36px; - padding: 0 11px; - cursor: pointer; - align-items: center; + align-items: stretch; justify-content: space-between; - border-radius: $radius; -} -.panel--name { - color: $g13-mist; - font-weight: 600; - font-size: 14px; - position: relative; - padding-left: 16px; - transition: color 0.25s ease; - - .icon.caret-right { - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%) rotate(0deg); - font-size: 0.75em; - transition: transform 0.25s ease; - } - - &:hover { - color: $g17-whisper; - } -} -.panel--actions { - display: flex; - align-items: center; -} -.panel--action { - width: 24px; - height: 24px; - border: 0; - background-color: transparent; - color: $g9-mountain; - margin-left: 2px; - transition: color 0.25s ease; - - &:hover { - cursor: pointer; - color: $g18-cloud; - } } // Tabs -.panel--tabs { +.query-builder--tabs { display: flex; + width: 190px; + flex-direction: column; + align-items: stretch; background-color: $g4-onyx; - padding: 0 11px; + border-radius: $radius 0 0 $radius; } -.panel--tab { +.query-builder--tab { + width: 100%; display: flex; align-items: center; color: $g11-sidewalk; @@ -173,7 +100,7 @@ max-width: 108px !important; } } -.panel--tab-label { +.query-builder--tab-label { display: inline-block; font-size: 12px; font-weight: 600; @@ -187,12 +114,48 @@ Tab Contents ------------------------------------------- */ -.panel--tab-contents { - padding: 6px; +.query-builder--tab-contents { + width: 100%; background-color: $g6-smoke; - border-radius: 0 0 $radius $radius; + border-radius: 0 $radius $radius 0; + overflow: hidden; + position: relative; } +.query-builder--columns { + position: absolute; + width: 100%; + height: calc(100% - 60px); + top: 60px; +} +.query-builder--column-heading { + width: 100%; + height: 30px; + position: absolute; + display: flex; + align-items: stretch; + justify-content: space-between; + top: 0; + & > div { + width: 25%; + height: 30px; + color: #f00; + } +} +.query-builder--column { + position: absolute; + width: 25%; + height: calc(100% - 30px); + top: 30px; + overflow: auto; + overflow-x: hidden; + overflow-y: scroll; + @include custom-scrollbar($g6-smoke,$c-pool); +} +.query-builder--column:nth-of-type(1) { left: 0; } +.query-builder--column:nth-of-type(2) { left: 25%; } +.query-builder--column:nth-of-type(3) { left: 50%; } +.query-builder--column:nth-of-type(4) { left: 75%; } /* Time Range Selector */ .time-range-dropdown { @@ -203,14 +166,6 @@ } } -.panel__header-actions { - display: flex; - - * { - margin-left: 5px; - } -} - .alert.alert-rawquery { border-color: $g5-pepper; border-color: $g6-smoke; diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index 8665bf3fb..618db6c6c 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -5,12 +5,12 @@ $graph-radius: 4px; $dygraphs-legend-offset: 32px; .graph { - position: relative; + position: absolute;; margin-bottom: 18px; - - &:last-child { - margin-bottom: 100%; - } + width: 100%; + padding: 0 $explorer-page-padding; + top:0; + height: 40%; } .graph-heading { background-color: $graph-bg-color; @@ -144,4 +144,4 @@ $dygraphs-legend-offset: 32px; background-color: $g6-smoke; color: $g14-chromium; } -} \ No newline at end of file +} From dfe8a1dfb3f6e823d02efabd03079fd7257755c2 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 13:36:36 -0800 Subject: [PATCH 03/30] Separate query from panel --- ui/src/data_explorer/actions/view/index.js | 8 +++----- ui/src/data_explorer/reducers/panels.js | 18 ------------------ ui/src/data_explorer/reducers/queryConfigs.js | 4 ++-- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/ui/src/data_explorer/actions/view/index.js b/ui/src/data_explorer/actions/view/index.js index f79fc537a..21d7d79c9 100644 --- a/ui/src/data_explorer/actions/view/index.js +++ b/ui/src/data_explorer/actions/view/index.js @@ -33,19 +33,17 @@ export function addQuery(panelId, options) { return { type: 'ADD_QUERY', payload: { - panelId, - queryId: uuid.v4(), + queryID: uuid.v4(), options, }, }; } -export function deleteQuery(panelId, queryId) { +export function deleteQuery(queryID) { return { type: 'DELETE_QUERY', payload: { - queryId, - panelId, + queryID, }, }; } diff --git a/ui/src/data_explorer/reducers/panels.js b/ui/src/data_explorer/reducers/panels.js index e04c58581..749c46493 100644 --- a/ui/src/data_explorer/reducers/panels.js +++ b/ui/src/data_explorer/reducers/panels.js @@ -27,24 +27,6 @@ export default function panels(state = {}, action) { return panelsCopy; }}); } - - case 'ADD_QUERY': { - const {panelId, queryId} = action.payload; - return update(state, { - [panelId]: { - queryIds: {$push: [queryId]}, - }, - }); - } - - case 'DELETE_QUERY': { - const {panelId, queryId} = action.payload; - return update(state, { - [panelId]: { - queryIds: {$set: state[panelId].queryIds.filter((id) => id !== queryId)}, - }, - }); - } } return state; diff --git a/ui/src/data_explorer/reducers/queryConfigs.js b/ui/src/data_explorer/reducers/queryConfigs.js index 82c14d0ec..dfb46e4b3 100644 --- a/ui/src/data_explorer/reducers/queryConfigs.js +++ b/ui/src/data_explorer/reducers/queryConfigs.js @@ -94,9 +94,9 @@ export default function queryConfigs(state = {}, action) { } case 'DELETE_QUERY': { - const {queryId} = action.payload; + const {queryID} = action.payload; const nextState = update(state, {$apply: (configs) => { - delete configs[queryId]; + delete configs[queryID]; return configs; }}); From 78b44592fa363bbc47cd9ff0763bcab3392951fb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 13:40:46 -0800 Subject: [PATCH 04/30] Turn QB tabs into columns --- .../data_explorer/components/DatabaseList.js | 25 ++-- ui/src/data_explorer/components/FieldList.js | 50 +++++--- .../components/MeasurementList.js | 45 +++++--- .../data_explorer/components/QueryBuilder.js | 13 +-- .../data_explorer/components/QueryEditor.js | 107 +++++------------- .../data_explorer/components/QueryTabItem.js | 6 +- ui/src/data_explorer/components/TagList.js | 32 +++++- 7 files changed, 142 insertions(+), 136 deletions(-) diff --git a/ui/src/data_explorer/components/DatabaseList.js b/ui/src/data_explorer/components/DatabaseList.js index c903290ea..ca08c79c1 100644 --- a/ui/src/data_explorer/components/DatabaseList.js +++ b/ui/src/data_explorer/components/DatabaseList.js @@ -60,18 +60,21 @@ const DatabaseList = React.createClass({ const {onChooseNamespace, query} = this.props; return ( -
    - {this.state.namespaces.map((namespace) => { - const {database, retentionPolicy} = namespace; - const isActive = database === query.database && retentionPolicy === query.retentionPolicy; +
    +
    Databases
    +
      + {this.state.namespaces.map((namespace) => { + const {database, retentionPolicy} = namespace; + const isActive = database === query.database && retentionPolicy === query.retentionPolicy; - return ( -
    • - {database}.{retentionPolicy} -
    • - ); - })} -
    + return ( +
  • + {database}.{retentionPolicy} +
  • + ); + })} +
+
); }, }); diff --git a/ui/src/data_explorer/components/FieldList.js b/ui/src/data_explorer/components/FieldList.js index ee700c638..df85ceef6 100644 --- a/ui/src/data_explorer/components/FieldList.js +++ b/ui/src/data_explorer/components/FieldList.js @@ -41,25 +41,26 @@ const FieldList = React.createClass({ }, componentDidMount() { - const {database, measurement, retentionPolicy} = this.props.query; + const {database, measurement} = this.props.query; if (!database || !measurement) { return; } - const {source} = this.context; - const proxySource = source.links.proxy; - showFieldKeys(proxySource, database, measurement, retentionPolicy).then((resp) => { - const {errors, fieldSets} = showFieldKeysParser(resp.data); - if (errors.length) { - // TODO: do something - } + this._getFields(); + }, - this.setState({ - fields: fieldSets[measurement].map((f) => { - return {field: f, funcs: []}; - }), - }); - }); + componentDidUpdate(prevProps) { + const {database, measurement, retentionPolicy} = this.props.query; + const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query; + if (!database || !measurement) { + return; + } + + if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) { + return; + } + + this._getFields(); }, handleGroupByTime(groupBy) { @@ -72,7 +73,7 @@ const FieldList = React.createClass({ const hasGroupByTime = query.groupBy.time; return ( -
+
{ hasAggregates ?
@@ -111,6 +112,25 @@ const FieldList = React.createClass({ ); }, + + _getFields() { + const {database, measurement, retentionPolicy} = this.props.query; + const {source} = this.context; + const proxySource = source.links.proxy; + + showFieldKeys(proxySource, database, measurement, retentionPolicy).then((resp) => { + const {errors, fieldSets} = showFieldKeysParser(resp.data); + if (errors.length) { + // TODO: do something + } + + this.setState({ + fields: fieldSets[measurement].map((f) => { + return {field: f, funcs: []}; + }), + }); + }); + }, }); export default FieldList; diff --git a/ui/src/data_explorer/components/MeasurementList.js b/ui/src/data_explorer/components/MeasurementList.js index 58e785721..93aff1442 100644 --- a/ui/src/data_explorer/components/MeasurementList.js +++ b/ui/src/data_explorer/components/MeasurementList.js @@ -34,19 +34,21 @@ const MeasurementList = React.createClass({ return; } - const {source} = this.context; - const proxy = source.links.proxy; - showMeasurements(proxy, this.props.query.database).then((resp) => { - const {errors, measurementSets} = showMeasurementsParser(resp.data); - if (errors.length) { - // TODO: display errors in the UI. - return console.error('InfluxDB returned error(s): ', errors); // eslint-disable-line no-console - } + this._getMeasurements(); + }, - this.setState({ - measurements: measurementSets[0].measurements, - }); - }); + componentDidUpdate(prevProps) { + const {query} = this.props; + + if (!query.database) { + return; + } + + if (prevProps.query.database === query.database) { + return; + } + + this._getMeasurements(); }, handleFilterText(e) { @@ -69,7 +71,7 @@ const MeasurementList = React.createClass({ render() { return ( -
+
{this.props.query.database ?
@@ -97,6 +99,23 @@ const MeasurementList = React.createClass({ ); }, + + _getMeasurements() { + const {source} = this.context; + const proxy = source.links.proxy; + showMeasurements(proxy, this.props.query.database).then((resp) => { + const {errors, measurementSets} = showMeasurementsParser(resp.data); + if (errors.length) { + // TODO: display errors in the UI. + return console.error('InfluxDB returned error(s): ', errors); // eslint-disable-line no-console + } + + this.setState({ + measurements: measurementSets[0].measurements, + }); + }); + }, + }); export default MeasurementList; diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js index c87116ce0..116ca61c8 100644 --- a/ui/src/data_explorer/components/QueryBuilder.js +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -59,16 +59,7 @@ const QueryBuilder = React.createClass({ render() { return ( -
-
-
- - {"Graph"} -
-
- {/*
*/} -
-
+
{this.renderQueryTabList()} {this.renderQueryEditor()}
@@ -102,7 +93,7 @@ const QueryBuilder = React.createClass({ renderQueryTabList() { const {queries} = this.props; return ( -
+
{queries.map((q) => { let queryTabText; if (q.rawText) { diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js index d3bcf4d7e..7c088b2b3 100644 --- a/ui/src/data_explorer/components/QueryEditor.js +++ b/ui/src/data_explorer/components/QueryEditor.js @@ -1,6 +1,4 @@ import React, {PropTypes} from 'react'; -import classNames from 'classnames'; -import _ from 'lodash'; import selectStatement from '../utils/influxql/select'; import DatabaseList from './DatabaseList'; @@ -9,12 +7,11 @@ import FieldList from './FieldList'; import TagList from './TagList'; import RawQueryEditor from './RawQueryEditor'; -const DB_TAB = 'databases'; -const MEASUREMENTS_TAB = 'measurments'; -const FIELDS_TAB = 'fields'; -const TAGS_TAB = 'tags'; - -const {string, shape, func} = PropTypes; +const { + string, + shape, + func, +} = PropTypes; const QueryEditor = React.createClass({ propTypes: { query: shape({ @@ -38,29 +35,17 @@ const QueryEditor = React.createClass({ getInitialState() { return { - activeTab: DB_TAB, database: null, measurement: null, }; }, - componentWillReceiveProps(nextProps) { - const changingQueries = this.props.query.id !== nextProps.query.id; - if (changingQueries) { - this.setState({activeTab: DB_TAB}); - } - }, - handleChooseNamespace(namespace) { this.props.actions.chooseNamespace(this.props.query.id, namespace); - - this.setState({activeTab: MEASUREMENTS_TAB}); }, handleChooseMeasurement(measurement) { this.props.actions.chooseMeasurement(this.props.query.id, measurement); - - this.setState({activeTab: FIELDS_TAB}); }, handleToggleField(field) { @@ -91,13 +76,9 @@ const QueryEditor = React.createClass({ this.props.actions.editRawText(this.props.query.id, text); }, - handleClickTab(tab) { - this.setState({activeTab: tab}); - }, - render() { return ( -
+
{this.renderQuery()} {this.renderLists()}
@@ -120,60 +101,32 @@ const QueryEditor = React.createClass({ }, renderLists() { - const {activeTab} = this.state; - return ( -
-
-
Schema Explorer
-
Databases
-
Measurements
-
Fields
-
Tags
-
- {this.renderList()} -
- ); - }, - - renderList() { const {query} = this.props; - switch (this.state.activeTab) { - case DB_TAB: - return ( - - ); - case MEASUREMENTS_TAB: - return ( - - ); - case FIELDS_TAB: - return ( - - ); - case TAGS_TAB: - return ( - - ); - default: - return
    ; - } + return ( +
    + + + + +
    + ); }, }); diff --git a/ui/src/data_explorer/components/QueryTabItem.js b/ui/src/data_explorer/components/QueryTabItem.js index c3ac85fe3..e8edaad04 100644 --- a/ui/src/data_explorer/components/QueryTabItem.js +++ b/ui/src/data_explorer/components/QueryTabItem.js @@ -23,9 +23,9 @@ const QueryTabItem = React.createClass({ render() { return ( -
    - {this.props.queryTabText} - +
    + {this.props.queryTabText} +
    ); }, diff --git a/ui/src/data_explorer/components/TagList.js b/ui/src/data_explorer/components/TagList.js index 666706417..ac9387e85 100644 --- a/ui/src/data_explorer/components/TagList.js +++ b/ui/src/data_explorer/components/TagList.js @@ -36,14 +36,11 @@ const TagList = React.createClass({ }; }, - componentDidMount() { + _getTags() { const {database, measurement, retentionPolicy} = this.props.query; const {source} = this.context; - if (!database || !measurement || !retentionPolicy) { - return; - } - const sourceProxy = source.links.proxy; + showTagKeys({source: sourceProxy, database, retentionPolicy, measurement}).then((resp) => { const {errors, tagKeys} = showTagKeysParser(resp.data); if (errors.length) { @@ -61,6 +58,29 @@ const TagList = React.createClass({ }); }, + componentDidMount() { + const {database, measurement, retentionPolicy} = this.props.query; + if (!database || !measurement || !retentionPolicy) { + return; + } + + this._getTags(); + }, + + componentDidUpdate(prevProps) { + const {database, measurement, retentionPolicy} = this.props.query; + const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query; + if (!database || !measurement || !retentionPolicy) { + return; + } + + if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) { + return; + } + + this._getTags(); + }, + handleAcceptReject(e) { e.stopPropagation(); this.props.onToggleTagAcceptance(); @@ -70,7 +90,7 @@ const TagList = React.createClass({ const {query} = this.props; return ( -
    +
    {(!query.database || !query.measurement || !query.retentionPolicy) ? null :
    Accept
    From 3ca2c903f59f4d3fe2bc13a45bc24e545bebba05 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 13:41:17 -0800 Subject: [PATCH 05/30] Use QueryBuilder and Visualization in DE --- .../data_explorer/containers/DataExplorer.js | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 516dd53b0..9caff7b0d 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -27,7 +27,6 @@ const DataExplorer = React.createClass({ upper: string, lower: string, }).isRequired, - activePanel: string, setTimeRange: func.isRequired, }, @@ -55,7 +54,8 @@ const DataExplorer = React.createClass({ }, render() { - const {timeRange, setTimeRange, activePanel, queryConfigs} = this.props; + const {timeRange, setTimeRange, queryConfigs} = this.props; + const {activeQueryID} = this.state; const queries = Object.keys(queryConfigs).map((q) => queryConfigs[q]); return ( @@ -64,18 +64,20 @@ const DataExplorer = React.createClass({ actions={{setTimeRange}} timeRange={timeRange} /> - - +
    + + +
    ); }, From c6050d4273e9899a38b2f1e34c85d015846a26e4 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 13:58:15 -0800 Subject: [PATCH 06/30] Remove concept of Panels --- .../reducers/dataExplorerUISpec.js | 11 -- ui/spec/data_explorer/reducers/panelSpec.js | 34 ---- ui/src/data_explorer/actions/view/index.js | 40 +--- ui/src/data_explorer/components/Panel.js | 183 ------------------ .../data_explorer/components/PanelBuilder.js | 63 ------ ui/src/data_explorer/components/PanelList.js | 76 -------- .../data_explorer/components/QueryBuilder.js | 2 - .../components/RenamePanelModal.js | 67 ------- .../data_explorer/components/Visualization.js | 2 +- .../components/Visualizations.js | 61 ------ .../data_explorer/containers/DataExplorer.js | 3 +- .../data_explorer/reducers/dataExplorerUI.js | 11 -- ui/src/data_explorer/reducers/index.js | 4 - ui/src/data_explorer/reducers/panels.js | 33 ---- ui/src/data_explorer/reducers/queryConfigs.js | 1 - ui/src/localStorage.js | 4 +- ui/src/style/pages/data-explorer.scss | 1 - .../pages/data-explorer/panel-builder.scss | 15 -- 18 files changed, 4 insertions(+), 607 deletions(-) delete mode 100644 ui/spec/data_explorer/reducers/dataExplorerUISpec.js delete mode 100644 ui/spec/data_explorer/reducers/panelSpec.js delete mode 100644 ui/src/data_explorer/components/Panel.js delete mode 100644 ui/src/data_explorer/components/PanelBuilder.js delete mode 100644 ui/src/data_explorer/components/PanelList.js delete mode 100644 ui/src/data_explorer/components/RenamePanelModal.js delete mode 100644 ui/src/data_explorer/components/Visualizations.js delete mode 100644 ui/src/data_explorer/reducers/dataExplorerUI.js delete mode 100644 ui/src/data_explorer/reducers/panels.js delete mode 100644 ui/src/style/pages/data-explorer/panel-builder.scss diff --git a/ui/spec/data_explorer/reducers/dataExplorerUISpec.js b/ui/spec/data_explorer/reducers/dataExplorerUISpec.js deleted file mode 100644 index b0f440467..000000000 --- a/ui/spec/data_explorer/reducers/dataExplorerUISpec.js +++ /dev/null @@ -1,11 +0,0 @@ -import reducer from 'src/data_explorer/reducers/dataExplorerUI'; -import {activatePanel} from 'src/data_explorer/actions/view'; - -describe('DataExplorer.Reducers.UI', () => { - it('can set the active panel', () => { - const activePanel = 123; - const actual = reducer({}, activatePanel(activePanel)); - - expect(actual).to.deep.equal({activePanel}); - }); -}); diff --git a/ui/spec/data_explorer/reducers/panelSpec.js b/ui/spec/data_explorer/reducers/panelSpec.js deleted file mode 100644 index 8182edd05..000000000 --- a/ui/spec/data_explorer/reducers/panelSpec.js +++ /dev/null @@ -1,34 +0,0 @@ -import reducer from 'src/data_explorer/reducers/panels'; -import {deletePanel} from 'src/data_explorer/actions/view'; - -const fakeAddPanelAction = (panelID, queryID) => { - return { - type: 'CREATE_PANEL', - payload: {panelID, queryID}, - }; -}; - -describe('Chronograf.Reducers.Panel', () => { - let state; - const panelID = 123; - const queryID = 456; - - beforeEach(() => { - state = reducer({}, fakeAddPanelAction(panelID, queryID)); - }); - - it('can add a panel', () => { - const actual = state[panelID]; - expect(actual).to.deep.equal({ - id: panelID, - queryIds: [queryID], - }); - }); - - it('can delete a panel', () => { - const nextState = reducer(state, deletePanel(panelID)); - - const actual = nextState[panelID]; - expect(actual).to.equal(undefined); - }); -}); diff --git a/ui/src/data_explorer/actions/view/index.js b/ui/src/data_explorer/actions/view/index.js index 21d7d79c9..8e5f36b22 100644 --- a/ui/src/data_explorer/actions/view/index.js +++ b/ui/src/data_explorer/actions/view/index.js @@ -1,35 +1,6 @@ import uuid from 'node-uuid'; -export function createPanel() { - return { - type: 'CREATE_PANEL', - payload: { - panelID: uuid.v4(), // for the default Panel - queryID: uuid.v4(), // for the default Query - }, - }; -} - -export function renamePanel(panelId, name) { - return { - type: 'RENAME_PANEL', - payload: { - panelId, - name, - }, - }; -} - -export function deletePanel(panelId) { - return { - type: 'DELETE_PANEL', - payload: { - panelId, - }, - }; -} - -export function addQuery(panelId, options) { +export function addQuery(options = {}) { return { type: 'ADD_QUERY', payload: { @@ -157,12 +128,3 @@ export function updateRawQuery(queryID, text) { }, }; } - -export function activatePanel(panelID) { - return { - type: 'ACTIVATE_PANEL', - payload: { - panelID, - }, - }; -} diff --git a/ui/src/data_explorer/components/Panel.js b/ui/src/data_explorer/components/Panel.js deleted file mode 100644 index 50ddf8621..000000000 --- a/ui/src/data_explorer/components/Panel.js +++ /dev/null @@ -1,183 +0,0 @@ -import React, {PropTypes} from 'react'; -import classNames from 'classnames'; -import QueryEditor from './QueryEditor'; -import QueryTabItem from './QueryTabItem'; -import RenamePanelModal from './RenamePanelModal'; -import SimpleDropdown from 'src/shared/components/SimpleDropdown'; - -const Panel = React.createClass({ - propTypes: { - panel: PropTypes.shape({ - id: PropTypes.string.isRequired, - }).isRequired, - queries: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - timeRange: PropTypes.shape({ - upper: PropTypes.string, - lower: PropTypes.string, - }).isRequired, - isExpanded: PropTypes.bool.isRequired, - onTogglePanel: PropTypes.func.isRequired, - actions: PropTypes.shape({ - chooseNamespace: PropTypes.func.isRequired, - chooseMeasurement: PropTypes.func.isRequired, - chooseTag: PropTypes.func.isRequired, - groupByTag: PropTypes.func.isRequired, - addQuery: PropTypes.func.isRequired, - deleteQuery: PropTypes.func.isRequired, - toggleField: PropTypes.func.isRequired, - groupByTime: PropTypes.func.isRequired, - toggleTagAcceptance: PropTypes.func.isRequired, - applyFuncsToField: PropTypes.func.isRequired, - deletePanel: PropTypes.func.isRequired, - renamePanel: PropTypes.func.isRequired, - }).isRequired, - setActiveQuery: PropTypes.func.isRequired, - activeQueryID: PropTypes.string, - }, - - handleSetActiveQuery(query) { - this.props.setActiveQuery(query.id); - }, - - handleAddQuery() { - this.props.actions.addQuery(); - }, - - handleAddRawQuery() { - this.props.actions.addQuery({rawText: `SELECT "fields" from "db"."rp"."measurement"`}); - }, - - handleDeleteQuery(query) { - this.props.actions.deleteQuery(query.id); - }, - - handleSelectPanel() { - this.props.onTogglePanel(this.props.panel); - }, - - handleDeletePanel(e) { - e.stopPropagation(); - this.props.actions.deletePanel(this.props.panel.id); - }, - - getActiveQuery() { - const {queries, activeQueryID} = this.props; - const activeQuery = queries.find((query) => query.id === activeQueryID); - const defaultQuery = queries[0]; - - return activeQuery || defaultQuery; - }, - - openRenamePanelModal(e) { - e.stopPropagation(); - $(`#renamePanelModal-${this.props.panel.id}`).modal('show'); // eslint-disable-line no-undef - }, - - handleRename(newName) { - this.props.actions.renamePanel(this.props.panel.id, newName); - }, - - - render() { - const {panel, isExpanded} = this.props; - - return ( -
    -
    -
    - - {panel.name || "Graph"} -
    -
    - {/*
    */} -
    -
    -
    -
    - {this.renderQueryTabList()} - {this.renderQueryEditor()} - -
    - ); - }, - - renderQueryEditor() { - if (!this.props.isExpanded) { - return null; - } - - const {timeRange, actions} = this.props; - const query = this.getActiveQuery(); - - if (!query) { - return ( -
    -
    This Graph has no Queries
    -
    -
    Add a Query
    -
    - ); - } - - return ( - - ); - }, - - renderQueryTabList() { - const {isExpanded, queries} = this.props; - if (!isExpanded) { - return null; - } - return ( -
    - {queries.map((q) => { - let queryTabText; - if (q.rawText) { - queryTabText = 'InfluxQL'; - } else { - queryTabText = (q.measurement && q.fields.length !== 0) ? `${q.measurement}.${q.fields[0].field}` : 'Query'; - } - return ( - - ); - })} - - {this.renderAddQuery()} -
    - ); - }, - - onChoose(item) { - switch (item.text) { - case 'Query Builder': - this.handleAddQuery(); - break; - case 'InfluxQL': - this.handleAddRawQuery(); - break; - } - }, - - renderAddQuery() { - return ( - - - - ); - }, -}); - -export default Panel; diff --git a/ui/src/data_explorer/components/PanelBuilder.js b/ui/src/data_explorer/components/PanelBuilder.js deleted file mode 100644 index e5b789070..000000000 --- a/ui/src/data_explorer/components/PanelBuilder.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, {PropTypes} from 'react'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import PanelList from './PanelList'; -import * as viewActions from '../actions/view'; - -const {string, func} = PropTypes; -const PanelBuilder = React.createClass({ - propTypes: { - width: string, - actions: PropTypes.shape({ - activatePanel: func.isRequired, - createPanel: func.isRequired, - deleteQuery: func.isRequired, - addQuery: func.isRequired, - editRawText: func.isRequired, - chooseNamespace: func.isRequired, - chooseMeasurement: func.isRequired, - toggleField: func.isRequired, - groupByTime: func.isRequired, - applyFuncsToField: func.isRequired, - chooseTag: func.isRequired, - groupByTag: func.isRequired, - toggleTagAcceptance: func.isRequired, - deletePanel: func.isRequired, - }).isRequired, - setActiveQuery: func.isRequired, - activePanelID: string, - activeQueryID: string, - }, - - handleCreateExplorer() { - this.props.actions.createPanel(); - }, - - render() { - const {width, actions, setActiveQuery, activePanelID, activeQueryID} = this.props; - - return ( -
    -
      Create Graph
    - -
    - ); - }, -}); - -function mapStateToProps() { - return {}; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators(viewActions, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(PanelBuilder); diff --git a/ui/src/data_explorer/components/PanelList.js b/ui/src/data_explorer/components/PanelList.js deleted file mode 100644 index c3d365e2d..000000000 --- a/ui/src/data_explorer/components/PanelList.js +++ /dev/null @@ -1,76 +0,0 @@ -import React, {PropTypes} from 'react'; -import {connect} from 'react-redux'; -import _ from 'lodash'; - -import Panel from './Panel'; - -const {func, string, shape} = PropTypes; -const PanelList = React.createClass({ - propTypes: { - timeRange: shape({ - upper: string, - lower: string, - }).isRequired, - panels: shape({}).isRequired, - queryConfigs: PropTypes.shape({}), - actions: shape({ - activatePanel: func.isRequired, - deleteQuery: func.isRequired, - addQuery: func.isRequired, - }).isRequired, - setActiveQuery: func.isRequired, - activePanelID: string, - activeQueryID: string, - }, - - handleTogglePanel(panel) { - const panelID = panel.id === this.props.activePanelID ? null : panel.id; - this.props.actions.activatePanel(panelID); - - // Reset the activeQueryID when toggling Exporations - this.props.setActiveQuery(null); - }, - - render() { - const {actions, panels, timeRange, queryConfigs, setActiveQuery, activeQueryID, activePanelID} = this.props; - - return ( -
    - {Object.keys(panels).map((panelID) => { - const panel = panels[panelID]; - const queries = panel.queryIds.map((configId) => queryConfigs[configId]); - const deleteQueryFromPanel = _.partial(actions.deleteQuery, panelID); - const addQueryToPanel = _.partial(actions.addQuery, panelID); - const allActions = Object.assign({}, actions, { - addQuery: addQueryToPanel, - deleteQuery: deleteQueryFromPanel, - }); - - return ( - - ); - })} -
    - ); - }, -}); - -function mapStateToProps(state) { - return { - timeRange: state.timeRange, - panels: state.panels, - queryConfigs: state.queryConfigs, - }; -} - -export default connect(mapStateToProps)(PanelList); diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js index 116ca61c8..45079bf86 100644 --- a/ui/src/data_explorer/components/QueryBuilder.js +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -26,8 +26,6 @@ const QueryBuilder = React.createClass({ groupByTime: PropTypes.func.isRequired, toggleTagAcceptance: PropTypes.func.isRequired, applyFuncsToField: PropTypes.func.isRequired, - deletePanel: PropTypes.func.isRequired, - renamePanel: PropTypes.func.isRequired, }).isRequired, setActiveQuery: PropTypes.func.isRequired, activeQueryID: PropTypes.string, diff --git a/ui/src/data_explorer/components/RenamePanelModal.js b/ui/src/data_explorer/components/RenamePanelModal.js deleted file mode 100644 index bffa495cc..000000000 --- a/ui/src/data_explorer/components/RenamePanelModal.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, {PropTypes} from 'react'; - -const RenamePanelModal = React.createClass({ - propTypes: { - onConfirm: PropTypes.func.isRequired, - panel: PropTypes.shape({ - id: PropTypes.string.isRequired, - }), - }, - - getInitialState() { - return {error: null}; - }, - - componentDidMount() { - this.refs.name.focus(); - }, - - render() { - const {panel} = this.props; - - return ( - - ); - }, - - handleConfirm() { - const name = this.refs.name.value; - - if (name === '') { - this.setState({error: "Name can't be blank"}); - return; - } - - $(`#renamePanelModal-${this.props.panel.id}`).modal('hide'); // eslint-disable-line no-undef - this.refs.name.value = ''; - this.setState({error: null}); - this.props.onConfirm(name); - }, -}); - -export default RenamePanelModal; diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 2a0705b70..74e590c0e 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -52,7 +52,7 @@ const Visualization = React.createClass({ const isInDataExplorer = true; return ( -
    this.panel = p} className={classNames("graph", {active: true})}> +
    {name || "Graph"} diff --git a/ui/src/data_explorer/components/Visualizations.js b/ui/src/data_explorer/components/Visualizations.js deleted file mode 100644 index aa577b0d6..000000000 --- a/ui/src/data_explorer/components/Visualizations.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, {PropTypes} from 'react'; -import {connect} from 'react-redux'; -import Visualization from './Visualization'; - -const {shape, string} = PropTypes; - -const Visualizations = React.createClass({ - propTypes: { - timeRange: shape({ - upper: string, - lower: string, - }).isRequired, - panels: shape({}).isRequired, - queryConfigs: shape({}).isRequired, - width: string, - activePanelID: string, - activeQueryID: string, - }, - - render() { - const {panels, queryConfigs, timeRange, width, activePanelID} = this.props; - - const visualizations = Object.keys(panels).map((panelID) => { - const panel = panels[panelID]; - const queries = panel.queryIds.map((id) => queryConfigs[id]); - const isActive = panelID === activePanelID; - - return ; - }); - - return ( -
    - {visualizations} -
    - ); - }, - - getActiveQueryIndex(panelID) { - const {activeQueryID, activePanelID, panels} = this.props; - const isPanelActive = panelID === activePanelID; - - if (!isPanelActive) { - return -1; - } - - if (activeQueryID === null) { - return 0; - } - - return panels[panelID].queryIds.indexOf(activeQueryID); - }, -}); - -function mapStateToProps(state) { - return { - panels: state.panels, - queryConfigs: state.queryConfigs, - }; -} - -export default connect(mapStateToProps)(Visualizations); diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index 9caff7b0d..a7617c290 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -84,12 +84,11 @@ const DataExplorer = React.createClass({ }); function mapStateToProps(state) { - const {timeRange, queryConfigs, dataExplorerUI} = state; + const {timeRange, queryConfigs} = state; return { timeRange, queryConfigs, - activePanel: dataExplorerUI.activePanel, }; } diff --git a/ui/src/data_explorer/reducers/dataExplorerUI.js b/ui/src/data_explorer/reducers/dataExplorerUI.js deleted file mode 100644 index 2140a9eba..000000000 --- a/ui/src/data_explorer/reducers/dataExplorerUI.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function dataExplorerUI(state = {}, action) { - switch (action.type) { - case 'ACTIVATE_PANEL': - case 'CREATE_PANEL': { - const {panelID} = action.payload; - return {...state, activePanel: panelID}; - } - } - - return state; -} diff --git a/ui/src/data_explorer/reducers/index.js b/ui/src/data_explorer/reducers/index.js index 4e4483f3b..4a0171f2a 100644 --- a/ui/src/data_explorer/reducers/index.js +++ b/ui/src/data_explorer/reducers/index.js @@ -1,11 +1,7 @@ import queryConfigs from './queryConfigs'; -import panels from './panels'; import timeRange from './timeRange'; -import dataExplorerUI from './dataExplorerUI'; export { queryConfigs, - panels, timeRange, - dataExplorerUI, }; diff --git a/ui/src/data_explorer/reducers/panels.js b/ui/src/data_explorer/reducers/panels.js deleted file mode 100644 index 749c46493..000000000 --- a/ui/src/data_explorer/reducers/panels.js +++ /dev/null @@ -1,33 +0,0 @@ -import update from 'react-addons-update'; - -export default function panels(state = {}, action) { - switch (action.type) { - case 'CREATE_PANEL': { - const {panelID, queryID} = action.payload; - return { - ...state, - [panelID]: {id: panelID, queryIds: [queryID]}, - }; - } - - case 'RENAME_PANEL': { - const {panelId, name} = action.payload; - return update(state, { - [panelId]: { - name: {$set: name}, - }, - }); - } - - case 'DELETE_PANEL': { - const {panelId} = action.payload; - return update(state, {$apply: (p) => { - const panelsCopy = Object.assign({}, p); - delete panelsCopy[panelId]; - return panelsCopy; - }}); - } - } - - return state; -} diff --git a/ui/src/data_explorer/reducers/queryConfigs.js b/ui/src/data_explorer/reducers/queryConfigs.js index dfb46e4b3..370923e4b 100644 --- a/ui/src/data_explorer/reducers/queryConfigs.js +++ b/ui/src/data_explorer/reducers/queryConfigs.js @@ -46,7 +46,6 @@ export default function queryConfigs(state = {}, action) { return nextState; } - case 'CREATE_PANEL': case 'ADD_KAPACITOR_QUERY': case 'ADD_QUERY': { const {queryID, options} = action.payload; diff --git a/ui/src/localStorage.js b/ui/src/localStorage.js index 2ab359365..64c2622d8 100644 --- a/ui/src/localStorage.js +++ b/ui/src/localStorage.js @@ -19,13 +19,11 @@ export const loadLocalStorage = () => { } }; -export const saveToLocalStorage = ({panels, queryConfigs, timeRange, dataExplorerUI}) => { +export const saveToLocalStorage = ({queryConfigs, timeRange}) => { try { window.localStorage.setItem('state', JSON.stringify({ - panels, queryConfigs, timeRange, - dataExplorerUI, })); } catch (err) { console.error('Unable to save data explorer: ', JSON.parse(err)); // eslint-disable-line no-console diff --git a/ui/src/style/pages/data-explorer.scss b/ui/src/style/pages/data-explorer.scss index 743af7d3b..7a60c4e56 100644 --- a/ui/src/style/pages/data-explorer.scss +++ b/ui/src/style/pages/data-explorer.scss @@ -29,7 +29,6 @@ // DE Specific components @import 'data-explorer/query-builder'; @import 'data-explorer/page-header'; -@import 'data-explorer/panel-builder'; @import 'data-explorer/query-editor'; @import 'data-explorer/raw-text'; @import 'data-explorer/tag-list'; diff --git a/ui/src/style/pages/data-explorer/panel-builder.scss b/ui/src/style/pages/data-explorer/panel-builder.scss deleted file mode 100644 index d71cf7bc7..000000000 --- a/ui/src/style/pages/data-explorer/panel-builder.scss +++ /dev/null @@ -1,15 +0,0 @@ -.panel-builder { - width: 399px; - overflow-x: hidden; - background: $g1-raven; - padding: $explorer-page-padding; - @include gradient-v($g2-kevlar,$g0-obsidian); - - &::-webkit-scrollbar { - display: none; - } - - > .btn { - margin-bottom: 6px; - } -} \ No newline at end of file From 31c64cbac7e5f0b8cf92596c47068fb8a9fa55bb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 8 Feb 2017 14:24:01 -0800 Subject: [PATCH 07/30] Add column name headers --- ui/src/data_explorer/components/FieldList.js | 32 ++++++++++--------- .../components/MeasurementList.js | 1 + ui/src/data_explorer/components/TagList.js | 1 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/ui/src/data_explorer/components/FieldList.js b/ui/src/data_explorer/components/FieldList.js index df85ceef6..8253d0bf6 100644 --- a/ui/src/data_explorer/components/FieldList.js +++ b/ui/src/data_explorer/components/FieldList.js @@ -74,6 +74,7 @@ const FieldList = React.createClass({ return (
    +
    Fields
    { hasAggregates ?
    @@ -95,21 +96,22 @@ const FieldList = React.createClass({ return
    No Measurement selected
    ; } - return (
      - {this.state.fields.map((fieldFunc) => { - const selectedField = this.props.query.fields.find((f) => f.field === fieldFunc.field); - return ( - - ); - })} -
    + return ( +
      + {this.state.fields.map((fieldFunc) => { + const selectedField = this.props.query.fields.find((f) => f.field === fieldFunc.field); + return ( + + ); + })} +
    ); }, diff --git a/ui/src/data_explorer/components/MeasurementList.js b/ui/src/data_explorer/components/MeasurementList.js index 93aff1442..986d10151 100644 --- a/ui/src/data_explorer/components/MeasurementList.js +++ b/ui/src/data_explorer/components/MeasurementList.js @@ -72,6 +72,7 @@ const MeasurementList = React.createClass({ render() { return (
    +
    Measurement
    {this.props.query.database ?
    diff --git a/ui/src/data_explorer/components/TagList.js b/ui/src/data_explorer/components/TagList.js index ac9387e85..42b60adf5 100644 --- a/ui/src/data_explorer/components/TagList.js +++ b/ui/src/data_explorer/components/TagList.js @@ -91,6 +91,7 @@ const TagList = React.createClass({ return (
    +
    Fields
    {(!query.database || !query.measurement || !query.retentionPolicy) ? null :
    Accept
    From d0684b4e977a7a067fd756f8040fbfc52db405d9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 8 Feb 2017 15:22:35 -0800 Subject: [PATCH 08/30] Make DE graph huge --- ui/src/shared/components/LineGraph.js | 1 - .../pages/data-explorer/visualization.scss | 33 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 20bba644c..ea36c088d 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -79,7 +79,6 @@ export default React.createClass({ labels, connectSeparatedPoints: true, labelsKMB: true, - height: 300, axisLineColor: '#383846', gridLineColor: '#383846', title, diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index 618db6c6c..2b51a8020 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -1,16 +1,15 @@ $graph-bg-color: $g3-castle; $graph-active-color: $g4-onyx; $graph-radius: 4px; - +$de-vertical-margin: 16px; $dygraphs-legend-offset: 32px; .graph { - position: absolute;; - margin-bottom: 18px; - width: 100%; - padding: 0 $explorer-page-padding; - top:0; - height: 40%; + position: absolute; + width: calc(100% - #{($explorer-page-padding * 2)}); + left: $explorer-page-padding; + top: $de-vertical-margin; + height: 60%; } .graph-heading { background-color: $graph-bg-color; @@ -48,6 +47,26 @@ $dygraphs-legend-offset: 32px; transition: background-color 0.25s ease; } +.data-explorer .graph-container { + height: calc(100% - 44px - 32px); + padding: 0; + + & > div { + position: absolute; + width: calc(100% - #{($de-vertical-margin * 2)}); + height: calc(100% - #{$de-vertical-margin}); + top: ($de-vertical-margin / 2); + left: $de-vertical-margin; + } + & > div > div { + position: absolute; + width: 100%; + height: 100%; + } + & > div > div > div:first-child { + height: 100% !important; + } +} /* Active State */ From 39f2c6a317b915e236f9b4185cd7957fcb130443 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 8 Feb 2017 15:22:49 -0800 Subject: [PATCH 09/30] Style queries tab list in DE --- .../data_explorer/components/QueryBuilder.js | 6 +- .../pages/data-explorer/query-builder.scss | 60 ++++++++++++------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js index 45079bf86..fb3787952 100644 --- a/ui/src/data_explorer/components/QueryBuilder.js +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -92,6 +92,10 @@ const QueryBuilder = React.createClass({ const {queries} = this.props; return (
    +
    +

    Queries

    + {this.renderAddQuery()} +
    {queries.map((q) => { let queryTabText; if (q.rawText) { @@ -110,8 +114,6 @@ const QueryBuilder = React.createClass({ /> ); })} - - {this.renderAddQuery()}
    ); }, diff --git a/ui/src/style/pages/data-explorer/query-builder.scss b/ui/src/style/pages/data-explorer/query-builder.scss index 5449ebce2..abef8bbb2 100644 --- a/ui/src/style/pages/data-explorer/query-builder.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -18,35 +18,49 @@ width: 190px; flex-direction: column; align-items: stretch; - background-color: $g4-onyx; + @include gradient-v($g3-castle,$g1-raven); border-radius: $radius 0 0 $radius; } +.query-builder--tabs-heading { + height: 60px; + padding: 0 9px; + display: flex; + align-items: center; + justify-content: space-between; + + h1 { + font-size: 17px; + font-weight: 400; + text-transform: uppercase; + color: $g18-cloud; + margin: 0; + } +} .query-builder--tab { width: 100%; display: flex; align-items: center; + justify-content: space-between; color: $g11-sidewalk; - background: $g5-pepper; - height: 28px; - margin-right: 2px; - border-radius: $radius $radius 0 0; + background: transparent; + height: 30px; cursor: pointer; - padding: 0 8px 0 8px; + padding: 0 8px; transition: color 0.25s ease, background-color 0.25s ease; &:hover { - color: $g18-cloud; - background-color: $g6-smoke; + color: $g15-platinum; + background-color: $g4-onyx; } &.active { - background: $g6-smoke; - color: $g15-platinum; + color: $g18-cloud; + background: $g5-pepper; } &-delete { - margin: 0 -4px 0 1px; + margin: 0; width: 16px; height: 16px; background-color: transparent; @@ -84,8 +98,11 @@ } .panel--tab-new { > .dropdown-toggle { - height: 28px !important; - border-radius: $radius $radius 0 0; + height: 30px !important; + border-radius: $radius; + background-color: $c-pool; + color: $g20-white !important; + padding: 0; > .icon { margin: 0; @@ -93,6 +110,9 @@ position: relative; top: -1px; } + &:hover { + background-color: $c-laser; + } } > .dropdown-menu { width: 108px !important; @@ -100,6 +120,13 @@ max-width: 108px !important; } } +.panel--tab-new.open { + > .dropdown-toggle, + > .dropdown-toggle:hover { + background-color: $c-laser !important; + color: $g20-white !important; + } +} .query-builder--tab-label { display: inline-block; font-size: 12px; @@ -130,17 +157,10 @@ .query-builder--column-heading { width: 100%; height: 30px; - position: absolute; display: flex; align-items: stretch; justify-content: space-between; - top: 0; - & > div { - width: 25%; - height: 30px; - color: #f00; - } } .query-builder--column { position: absolute; From 6e4f8f0cb432d4c5240c4c35338312317b167dd0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 8 Feb 2017 15:50:11 -0800 Subject: [PATCH 10/30] Clean up the DE columns --- .../components/MeasurementList.js | 2 +- .../data_explorer/components/QueryEditor.js | 8 ++-- ui/src/data_explorer/components/TagList.js | 2 +- .../pages/data-explorer/query-builder.scss | 40 ++++++++++++++----- .../pages/data-explorer/query-editor.scss | 17 ++++---- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/ui/src/data_explorer/components/MeasurementList.js b/ui/src/data_explorer/components/MeasurementList.js index 986d10151..d3eaf8d78 100644 --- a/ui/src/data_explorer/components/MeasurementList.js +++ b/ui/src/data_explorer/components/MeasurementList.js @@ -72,7 +72,7 @@ const MeasurementList = React.createClass({ render() { return (
    -
    Measurement
    +
    Measurements
    {this.props.query.database ?
    diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js index 7c088b2b3..1d8046c51 100644 --- a/ui/src/data_explorer/components/QueryEditor.js +++ b/ui/src/data_explorer/components/QueryEditor.js @@ -79,8 +79,10 @@ const QueryEditor = React.createClass({ render() { return (
    - {this.renderQuery()} - {this.renderLists()} +
    + {this.renderQuery()} + {this.renderLists()} +
    ); }, @@ -91,7 +93,7 @@ const QueryEditor = React.createClass({ if (!query.rawText) { return ( -
    +
    {statement}
    ); diff --git a/ui/src/data_explorer/components/TagList.js b/ui/src/data_explorer/components/TagList.js index 42b60adf5..dda71080a 100644 --- a/ui/src/data_explorer/components/TagList.js +++ b/ui/src/data_explorer/components/TagList.js @@ -91,7 +91,7 @@ const TagList = React.createClass({ return (
    -
    Fields
    +
    Tags
    {(!query.database || !query.measurement || !query.retentionPolicy) ? null :
    Accept
    diff --git a/ui/src/style/pages/data-explorer/query-builder.scss b/ui/src/style/pages/data-explorer/query-builder.scss index abef8bbb2..c3264949e 100644 --- a/ui/src/style/pages/data-explorer/query-builder.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -143,11 +143,18 @@ */ .query-builder--tab-contents { width: 100%; - background-color: $g6-smoke; + background-color: $g5-pepper; border-radius: 0 $radius $radius 0; overflow: hidden; position: relative; } +.query-builder--tab-contents > div { + position: absolute; + top: 4px; + left: 4px; + width: calc(100% - 8px); + height: calc(100% - 8px); +} .query-builder--columns { position: absolute; width: 100%; @@ -158,19 +165,34 @@ width: 100%; height: 30px; display: flex; - align-items: stretch; + align-items: center; justify-content: space-between; - + font-size: 14px; + font-weight: 600; + color: $g13-mist; + padding: 0 9px; + line-height: 30px; + border-bottom: 2px solid $g6-smoke; } .query-builder--column { position: absolute; width: 25%; - height: calc(100% - 30px); - top: 30px; - overflow: auto; - overflow-x: hidden; - overflow-y: scroll; - @include custom-scrollbar($g6-smoke,$c-pool); + height: 100%; + top: 0; + + .qeditor--list { + position: absolute; + top: 30px; + height: calc(100% - 30px); + width: 100%; + left: 0; + padding: 0; + overflow: auto; + overflow-x: hidden; + overflow-y: scroll; + @include custom-scrollbar($g5-pepper,$c-pool); + background-color: $g5-pepper; + } } .query-builder--column:nth-of-type(1) { left: 0; } .query-builder--column:nth-of-type(2) { left: 25%; } diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index 80172b717..f1c3ac6c8 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -10,7 +10,7 @@ $query-editor-tab-inactive: $g2-kevlar; $query-editor-tab-active: $g3-castle; $query-editor-height: 250px; -.qeditor--query-preview { +.query-builder--query-preview { position: relative; pre { @@ -18,11 +18,10 @@ $query-editor-height: 250px; border: 0; background-color: $query-editor-tab-inactive; color: $c-pool; - border-radius: $radius-small $radius-small 0 0; - border-bottom: 2px solid $query-editor-tab-active; + border-radius: $radius; margin-bottom: 0; overflow: auto; - min-height: 3.25em; + height: 56px; @include custom-scrollbar($query-editor-tab-inactive, $c-pool); code { @@ -90,7 +89,8 @@ $query-editor-height: 250px; list-style-type: none; margin: 0; font-size: 12px; - padding: 4px 9px 4px 18px; + font-weight: 500; + padding: 4px 9px; transition: color 0.25s ease, background-color 0.25s ease; @@ -105,7 +105,7 @@ $query-editor-height: 250px; &.active { color: $g20-white; background-color: $g4-onyx; - font-weight: 600; + font-weight: 700; } } &-checkbox { @@ -178,14 +178,13 @@ $query-editor-height: 250px; // List empty state .qeditor--empty { - text-align: center; + text-align: left; color: $g10-wolf; display: inline-block; width: 100%; padding: 18px 0; height: $query-editor-height; - background-color: $query-editor-tab-active; - border-radius: 0 0 $radius $radius; + background-color: $g5-pepper; } // Hidden dropdowns From 232633da8fd14ad72a94984b206a8a7f9d47be8b Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 08:47:11 -0800 Subject: [PATCH 11/30] Fix vertical marker in DE --- ui/src/style/pages/data-explorer/visualization.scss | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index 2b51a8020..7db5b62e3 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -53,15 +53,18 @@ $dygraphs-legend-offset: 32px; & > div { position: absolute; - width: calc(100% - #{($de-vertical-margin * 2)}); - height: calc(100% - #{$de-vertical-margin}); - top: ($de-vertical-margin / 2); - left: $de-vertical-margin; + width: 100%; + height: 100%; } & > div > div { position: absolute; width: 100%; height: 100%; + padding: 8px 16px; + // width: calc(100% - #{($de-vertical-margin * 2)}); + // height: calc(100% - #{$de-vertical-margin}); + // top: ($de-vertical-margin / 2); + // left: $de-vertical-margin; } & > div > div > div:first-child { height: 100% !important; From b2a3b8ce890ce6d91de8212944b7ba491c822baa Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 09:14:07 -0800 Subject: [PATCH 12/30] Fix positioning of query editor list headers measurements filter, group by time, and accept/reject --- .../components/MeasurementList.js | 2 +- .../pages/data-explorer/query-builder.scss | 23 ++++++++------- .../pages/data-explorer/query-editor.scss | 29 ++++++++++++------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/ui/src/data_explorer/components/MeasurementList.js b/ui/src/data_explorer/components/MeasurementList.js index d3eaf8d78..91521eeac 100644 --- a/ui/src/data_explorer/components/MeasurementList.js +++ b/ui/src/data_explorer/components/MeasurementList.js @@ -74,7 +74,7 @@ const MeasurementList = React.createClass({
    Measurements
    {this.props.query.database ?
    - +
    : null } {this.renderList()} diff --git a/ui/src/style/pages/data-explorer/query-builder.scss b/ui/src/style/pages/data-explorer/query-builder.scss index c3264949e..ec7b98317 100644 --- a/ui/src/style/pages/data-explorer/query-builder.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -15,7 +15,7 @@ // Tabs .query-builder--tabs { display: flex; - width: 190px; + width: 250px; flex-direction: column; align-items: stretch; @include gradient-v($g3-castle,$g1-raven); @@ -23,7 +23,7 @@ } .query-builder--tabs-heading { height: 60px; - padding: 0 9px; + padding: 0 9px 0 16px; display: flex; align-items: center; justify-content: space-between; @@ -45,7 +45,7 @@ background: transparent; height: 30px; cursor: pointer; - padding: 0 8px; + padding: 0 8px 0 16px; transition: color 0.25s ease, background-color 0.25s ease; @@ -141,9 +141,10 @@ Tab Contents ------------------------------------------- */ +$query-builder--column-heading-height: 60px; .query-builder--tab-contents { width: 100%; - background-color: $g5-pepper; + background-color: $g4-onyx; border-radius: 0 $radius $radius 0; overflow: hidden; position: relative; @@ -163,7 +164,7 @@ } .query-builder--column-heading { width: 100%; - height: 30px; + height: $query-builder--column-heading-height; display: flex; align-items: center; justify-content: space-between; @@ -171,8 +172,8 @@ font-weight: 600; color: $g13-mist; padding: 0 9px; - line-height: 30px; - border-bottom: 2px solid $g6-smoke; + line-height: $query-builder--column-heading-height; + border-bottom: 2px solid $g5-pepper; } .query-builder--column { position: absolute; @@ -182,16 +183,16 @@ .qeditor--list { position: absolute; - top: 30px; - height: calc(100% - 30px); + top: $query-builder--column-heading-height; + height: calc(100% - #{$query-builder--column-heading-height}); width: 100%; left: 0; padding: 0; overflow: auto; overflow-x: hidden; overflow-y: scroll; - @include custom-scrollbar($g5-pepper,$c-pool); - background-color: $g5-pepper; + @include custom-scrollbar($g4-onyx,$c-pool); + background-color: $g4-onyx; } } .query-builder--column:nth-of-type(1) { left: 0; } diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index f1c3ac6c8..bcb8556e2 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -96,15 +96,15 @@ $query-editor-height: 250px; background-color 0.25s ease; &:hover { - background-color: $g4-onyx; - color: $g17-whisper; + background-color: $g6-smoke; + color: $g15-platinum; cursor: pointer; } } &-radio { &.active { color: $g20-white; - background-color: $g4-onyx; + background-color: $g6-smoke; font-weight: 700; } } @@ -169,11 +169,18 @@ $query-editor-height: 250px; } } } - &-header { - position: relative; - background-color: $query-editor-tab-active; - padding: 8px 18px 0px 18px; - } +} +.qeditor--list-header { + position: absolute; + top: 15px; + right: 16px; + width: calc(60% - 16px); + height: 30px; + padding: 0; + z-index: 10; + display: flex; + align-items: center; + justify-content: flex-end; } // List empty state @@ -209,7 +216,7 @@ $query-editor-height: 250px; height: 30px; border-radius: 15px; font-size: 13px; - padding-left: 38px; + padding-left: 28px; outline: none; color: $g20-white; font-weight: 700; @@ -243,8 +250,8 @@ $query-editor-height: 250px; } + .icon { position: absolute; - top: calc(50% + 5px); - left: calc(19px * 2); + top: 50%; + left: 11px; transform: translateY(-50%); color: $g10-wolf; transition: color 0.25s ease; From 4e24367b774f2d9aa82a583133d94ea0274bd710 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 09:17:35 -0800 Subject: [PATCH 13/30] Polish query editor list items --- ui/src/style/pages/data-explorer/query-editor.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index bcb8556e2..3f2aa577d 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -96,7 +96,7 @@ $query-editor-height: 250px; background-color 0.25s ease; &:hover { - background-color: $g6-smoke; + background-color: $g5-pepper; color: $g15-platinum; cursor: pointer; } @@ -104,7 +104,7 @@ $query-editor-height: 250px; &-radio { &.active { color: $g20-white; - background-color: $g6-smoke; + background-color: $g5-pepper; font-weight: 700; } } From 7aafeaf8dd69b832bf3a9b23c5dd8344ce8a0ff6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 09:53:48 -0800 Subject: [PATCH 14/30] Fix Kapacitor Rule Builder weirdness --- ui/src/style/pages/kapacitor.scss | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ui/src/style/pages/kapacitor.scss b/ui/src/style/pages/kapacitor.scss index 3133a498a..6e4a03d35 100644 --- a/ui/src/style/pages/kapacitor.scss +++ b/ui/src/style/pages/kapacitor.scss @@ -219,6 +219,34 @@ div.qeditor.kapacitor-metric-selector { .kapacitor-tab-list { background-color: $kapacitor-graphic-color; border-radius: 0 0 $kap-radius-lg $kap-radius-lg; + + .query-builder--column { + position: relative; + top: initial; + left: initial; + width: 100%; + height: 190px; + + .qeditor--list-header { + width: 50%; + top: -34px; + right: 0; + z-index: 5; + } + .qeditor--list { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + .group-by-time-dropdown { + width: 70px; + } + } + .query-builder--column-heading { + display: none; + } } .qeditor--list { overflow: auto; From 32ffc88a8b6f92f8070b5ad4bc23656db9e9dc7d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 9 Feb 2017 11:15:51 -0800 Subject: [PATCH 15/30] Improve and clarify Group BY tags --- ui/src/data_explorer/components/QueryBuilder.js | 4 ++-- ui/src/data_explorer/components/TagListItem.js | 16 +++++++++++----- .../style/pages/data-explorer/query-editor.scss | 6 +++--- ui/src/style/pages/data-explorer/tag-list.scss | 13 ++++++++++--- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js index fb3787952..63eaa5dd5 100644 --- a/ui/src/data_explorer/components/QueryBuilder.js +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -96,7 +96,7 @@ const QueryBuilder = React.createClass({

    Queries

    {this.renderAddQuery()}
    - {queries.map((q) => { + {queries.map((q, i) => { let queryTabText; if (q.rawText) { queryTabText = 'InfluxQL'; @@ -106,7 +106,7 @@ const QueryBuilder = React.createClass({ return (
  • @@ -89,12 +92,15 @@ const TagListItem = React.createClass({
    - {this.props.tagKey} - {this.props.tagValues.length} + {tagKey} + {tagValues.length} +
  • +
    Group By {tagKey}
    -
    Group By
    - {this.state.isOpen ? this.renderTagValues() : null} + {isOpen ? this.renderTagValues() : null}
    ); }, diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index 3f2aa577d..729086039 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -185,13 +185,13 @@ $query-editor-height: 250px; // List empty state .qeditor--empty { - text-align: left; + text-align: center; color: $g10-wolf; display: inline-block; width: 100%; padding: 18px 0; height: $query-editor-height; - background-color: $g5-pepper; + background-color: transparent; } // Hidden dropdowns @@ -258,4 +258,4 @@ $query-editor-height: 250px; font-size: 12px; z-index: 2; } -} \ No newline at end of file +} diff --git a/ui/src/style/pages/data-explorer/tag-list.scss b/ui/src/style/pages/data-explorer/tag-list.scss index 3dbf18c8e..1d011cc14 100644 --- a/ui/src/style/pages/data-explorer/tag-list.scss +++ b/ui/src/style/pages/data-explorer/tag-list.scss @@ -1,10 +1,15 @@ .tag-list { &__item { + height: 30px; display: flex; align-items: center; justify-content: space-between; + &:hover .tag-list__group-by { + display: flex; + } + &.open { font-weight: 600; color: $g20-white; @@ -63,7 +68,7 @@ opacity: 0; background-color: $c-pool; border-radius: 50%; - transition: + transition: transform 0.25s ease, opacity 0.25s ease; } @@ -124,7 +129,7 @@ color: $g10-wolf; font-weight: 500; } - &:-ms-input-placeholder { + &:-ms-input-placeholder { color: $g10-wolf; font-weight: 500; } @@ -148,6 +153,7 @@ } .tag-list__group-by { + display: none; background-color: $g5-pepper; border-color: $g5-pepper; color: $g13-mist !important; @@ -165,6 +171,7 @@ border-color: $g6-smoke; } &.active { + display: flex; background: $c-pool; border-color: $c-pool; @@ -173,4 +180,4 @@ border-color: $c-laser; } } -} \ No newline at end of file +} From 0f3b0b4caa86834f21ff63da2e1bcc2670e47a36 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 9 Feb 2017 11:17:37 -0800 Subject: [PATCH 16/30] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f072a7385..a9cab57fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ 2. [#853](https://github.com/influxdata/chronograf/issues/853): Updated builds to use yarn over npm install ### Upcoming UI Improvements + 1. [#822](https://github.com/influxdata/chronograf/issues/822): Simplify and improve layout of the Data Explorer + - The Data Explorer's intention and purpose has always been the ad hoc and ephemeral exploration of your schema and data. + The concept of `Exploration` sessions and `Panels` betrayed this initial intention. The DE turned into a "poor man's" + dashboarding tool. In turn, this introduced complexity in the code and the UI. In the future if I want to save, manipulate, + and view multiple visualizations this will be done more efficiently and effectively in our dashboarding solution. ## v1.2.0-beta1 [2017-01-27] From b14c744d0a374753d97d1a5c5d238b2f01fdb3a2 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 9 Feb 2017 11:42:13 -0800 Subject: [PATCH 17/30] Update copy --- ui/src/data_explorer/components/TagList.js | 4 ++-- ui/src/style/theme/theme-dark.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/data_explorer/components/TagList.js b/ui/src/data_explorer/components/TagList.js index dda71080a..066b0d00f 100644 --- a/ui/src/data_explorer/components/TagList.js +++ b/ui/src/data_explorer/components/TagList.js @@ -94,8 +94,8 @@ const TagList = React.createClass({
    Tags
    {(!query.database || !query.measurement || !query.retentionPolicy) ? null :
    -
    Accept
    -
    Reject
    +
    =
    +
    !=
    } {this.renderList()} diff --git a/ui/src/style/theme/theme-dark.scss b/ui/src/style/theme/theme-dark.scss index 80fccd25d..361c833c0 100644 --- a/ui/src/style/theme/theme-dark.scss +++ b/ui/src/style/theme/theme-dark.scss @@ -443,7 +443,7 @@ $toggle-border: 2px; .toggle-btn { height: ($toggle-height-sm - ($toggle-border * 2)); line-height: ($toggle-height-sm - ($toggle-border * 2)); - padding: 0 $toggle-padding-sm; + padding: 0 16px; font-size: $toggle-font-sm; font-weight: 600; } From 0b8c334c9d7c5fff13b2732a0ffb95fb8cd9aefa Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 14:35:38 -0600 Subject: [PATCH 18/30] Add gzip compression to all of our server respones --- Godeps | 1 + LICENSE_OF_DEPENDENCIES.md | 1 + server/mux.go | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Godeps b/Godeps index f2c6cb8ba..a81e679b1 100644 --- a/Godeps +++ b/Godeps @@ -1,3 +1,4 @@ +github.com/NYTimes/gziphandler 6710af535839f57c687b62c4c23d649f9545d885 github.com/Sirupsen/logrus 3ec0642a7fb6488f65b06f9040adc67e3990296a github.com/boltdb/bolt 5cc10bbbc5c141029940133bb33c9e969512a698 github.com/bouk/httprouter ee8b3818a7f51fbc94cc709b5744b52c2c725e91 diff --git a/LICENSE_OF_DEPENDENCIES.md b/LICENSE_OF_DEPENDENCIES.md index 5efd655a6..0f96a24c3 100644 --- a/LICENSE_OF_DEPENDENCIES.md +++ b/LICENSE_OF_DEPENDENCIES.md @@ -1,4 +1,5 @@ ### Go +* github.com/NYTimes/gziphandler [APACHE-2.0](https://github.com/NYTimes/gziphandler/blob/master/LICENSE.md) * github.com/Sirupsen/logrus [MIT](https://github.com/Sirupsen/logrus/blob/master/LICENSE) * github.com/boltdb/bolt [MIT](https://github.com/boltdb/bolt/blob/master/LICENSE) * github.com/bouk/httprouter [BSD](https://github.com/bouk/httprouter/blob/master/LICENSE) diff --git a/server/mux.go b/server/mux.go index 927f93579..0c28a5302 100644 --- a/server/mux.go +++ b/server/mux.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/NYTimes/gziphandler" "github.com/bouk/httprouter" "github.com/influxdata/chronograf" // When julienschmidt/httprouter v2 w/ context is out, switch "github.com/influxdata/chronograf/jwt" @@ -119,7 +120,10 @@ func NewMux(opts MuxOpts, service Service) http.Handler { auth := AuthAPI(opts, router) return Logger(opts.Logger, auth) } - return Logger(opts.Logger, router) + + compressed := gziphandler.GzipHandler(router) + logged := Logger(opts.Logger, compressed) + return logged } // AuthAPI adds the OAuth routes if auth is enabled. From c721de4167a5fc20b5891d18f7a251ecb5bee837 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 12:50:57 -0800 Subject: [PATCH 19/30] Change groupbytime bg color to match adjacent toggles --- ui/src/style/components/group-by-time-dropdown.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/style/components/group-by-time-dropdown.scss b/ui/src/style/components/group-by-time-dropdown.scss index 134be12dc..12650a756 100644 --- a/ui/src/style/components/group-by-time-dropdown.scss +++ b/ui/src/style/components/group-by-time-dropdown.scss @@ -1,4 +1,4 @@ -.group-by-time-dropdown .dropdown-toggle { + .group-by-time-dropdown .dropdown-toggle { width: 70px; } .group-by-time { @@ -17,6 +17,7 @@ border-style: solid; border-color: $g5-pepper; border-width: 2px; + background-color: $g3-castle; } .dropdown-toggle { border-radius: 0px 3px 3px 0; From 5d6fd473a834ec0a292f76aedfe506e27d44eeba Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 9 Feb 2017 13:51:52 -0800 Subject: [PATCH 20/30] Introduce ResizerHandle back into DE --- .../data_explorer/components/QueryBuilder.js | 45 +++++++++++-------- .../data_explorer/components/Visualization.js | 24 ++++++---- .../data_explorer/containers/DataExplorer.js | 5 ++- ui/src/shared/components/ResizeContainer.js | 36 +++++++-------- ui/src/shared/components/ResizeHandle.js | 13 +++++- ui/src/style/components/resizer.scss | 26 +++++------ ui/src/style/pages/data-explorer.scss | 10 +++++ .../pages/data-explorer/query-builder.scss | 8 ++-- .../pages/data-explorer/query-editor.scss | 4 -- .../pages/data-explorer/visualization.scss | 15 +++---- 10 files changed, 109 insertions(+), 77 deletions(-) diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js index 63eaa5dd5..97b29958a 100644 --- a/ui/src/data_explorer/components/QueryBuilder.js +++ b/ui/src/data_explorer/components/QueryBuilder.js @@ -7,28 +7,36 @@ import QueryTabItem from './QueryTabItem'; import SimpleDropdown from 'src/shared/components/SimpleDropdown'; import * as viewActions from '../actions/view'; +const { + arrayOf, + func, + shape, + string, +} = PropTypes; const QueryBuilder = React.createClass({ propTypes: { - queries: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - timeRange: PropTypes.shape({ - upper: PropTypes.string, - lower: PropTypes.string, + queries: arrayOf(shape({})).isRequired, + timeRange: shape({ + upper: string, + lower: string, }).isRequired, - actions: PropTypes.shape({ - chooseNamespace: PropTypes.func.isRequired, - chooseMeasurement: PropTypes.func.isRequired, - chooseTag: PropTypes.func.isRequired, - groupByTag: PropTypes.func.isRequired, - addQuery: PropTypes.func.isRequired, - deleteQuery: PropTypes.func.isRequired, - toggleField: PropTypes.func.isRequired, - groupByTime: PropTypes.func.isRequired, - toggleTagAcceptance: PropTypes.func.isRequired, - applyFuncsToField: PropTypes.func.isRequired, + actions: shape({ + chooseNamespace: func.isRequired, + chooseMeasurement: func.isRequired, + chooseTag: func.isRequired, + groupByTag: func.isRequired, + addQuery: func.isRequired, + deleteQuery: func.isRequired, + toggleField: func.isRequired, + groupByTime: func.isRequired, + toggleTagAcceptance: func.isRequired, + applyFuncsToField: func.isRequired, }).isRequired, - setActiveQuery: PropTypes.func.isRequired, - activeQueryID: PropTypes.string, + height: string, + top: string, + setActiveQuery: func.isRequired, + activeQueryID: string, }, handleSetActiveQuery(query) { @@ -56,8 +64,9 @@ const QueryBuilder = React.createClass({ }, render() { + const {height, top} = this.props; return ( -
    +
    {this.renderQueryTabList()} {this.renderQueryEditor()}
    diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 74e590c0e..7c726d77c 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -6,15 +6,23 @@ import LineGraph from 'shared/components/LineGraph'; import MultiTable from './MultiTable'; const RefreshingLineGraph = AutoRefresh(LineGraph); +const { + arrayOf, + number, + shape, + string, +} = PropTypes; + const Visualization = React.createClass({ propTypes: { - timeRange: PropTypes.shape({ - upper: PropTypes.string, - lower: PropTypes.string, + timeRange: shape({ + upper: string, + lower: string, }).isRequired, - queryConfigs: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - name: PropTypes.string, - activeQueryIndex: PropTypes.number, + queryConfigs: arrayOf(shape({})).isRequired, + name: string, + activeQueryIndex: number, + height: string, }, contextTypes: { @@ -36,7 +44,7 @@ const Visualization = React.createClass({ }, render() { - const {queryConfigs, timeRange, activeQueryIndex} = this.props; + const {queryConfigs, timeRange, activeQueryIndex, height} = this.props; const {source} = this.context; const proxyLink = source.links.proxy; @@ -52,7 +60,7 @@ const Visualization = React.createClass({ const isInDataExplorer = true; return ( -
    +
    {name || "Graph"} diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js index a7617c290..1b56a7efb 100644 --- a/ui/src/data_explorer/containers/DataExplorer.js +++ b/ui/src/data_explorer/containers/DataExplorer.js @@ -3,6 +3,7 @@ import {connect} from 'react-redux'; import QueryBuilder from '../components/QueryBuilder'; import Visualization from '../components/Visualization'; import Header from '../containers/Header'; +import ResizeContainer from 'src/shared/components/ResizeContainer'; import { setTimeRange as setTimeRangeAction, @@ -64,7 +65,7 @@ const DataExplorer = React.createClass({ actions={{setTimeRange}} timeRange={timeRange} /> -
    + -
    +
    ); }, diff --git a/ui/src/shared/components/ResizeContainer.js b/ui/src/shared/components/ResizeContainer.js index 223402d58..d2cc60d5b 100644 --- a/ui/src/shared/components/ResizeContainer.js +++ b/ui/src/shared/components/ResizeContainer.js @@ -9,8 +9,8 @@ const ResizeContainer = React.createClass({ getInitialState() { return { - leftWidth: '34%', - rightWidth: '66%', + topHeight: '60%', + bottomHeight: '40%', isDragging: false, }; }, @@ -32,40 +32,40 @@ const ResizeContainer = React.createClass({ return; } - const appWidth = parseInt(getComputedStyle(this.refs.resizeContainer).width, 10); - // handleOffSet moves the resize handle as many pixels as the side bar is taking up. - const handleOffSet = window.innerWidth - appWidth; + const appHeight = parseInt(getComputedStyle(this.refs.resizeContainer).height, 10); + // headingOffset moves the resize handle as many pixels as the page-heading is taking up. + const headingOffset = window.innerHeight - appHeight; const turnToPercent = 100; - const newLeftPanelPercent = Math.ceil(((e.pageX - handleOffSet) / (appWidth)) * turnToPercent); - const newRightPanelPercent = (turnToPercent - newLeftPanelPercent); + const newTopPanelPercent = Math.ceil(((e.pageY - headingOffset) / (appHeight)) * turnToPercent); + const newBottomPanelPercent = (turnToPercent - newTopPanelPercent); // Don't trigger a resize unless the change in size is greater than minResizePercentage const minResizePercentage = 0.5; - if (Math.abs(newLeftPanelPercent - parseFloat(this.state.leftWidth)) < minResizePercentage) { + if (Math.abs(newTopPanelPercent - parseFloat(this.state.topHeight)) < minResizePercentage) { return; } // Don't trigger a resize if the new sizes are too small - const minLeftPanelWidth = 371; - const minRightPanelWidth = 389; - if (((newLeftPanelPercent / turnToPercent) * appWidth) < minLeftPanelWidth || ((newRightPanelPercent / turnToPercent) * appWidth) < minRightPanelWidth) { + const minTopPanelHeight = 300; + const minBottomPanelHeight = 250; + if (((newTopPanelPercent / turnToPercent) * appHeight) < minTopPanelHeight || ((newBottomPanelPercent / turnToPercent) * appHeight) < minBottomPanelHeight) { return; } - this.setState({leftWidth: `${(newLeftPanelPercent)}%`, rightWidth: `${(newRightPanelPercent)}%`}); + this.setState({topHeight: `${(newTopPanelPercent)}%`, bottomHeight: `${(newBottomPanelPercent)}%`}); }, render() { - const {leftWidth, rightWidth, isDragging} = this.state; - const left = React.cloneElement(this.props.children[0], {width: leftWidth}); - const right = React.cloneElement(this.props.children[1], {width: rightWidth}); - const handle = ; + const {topHeight, bottomHeight, isDragging} = this.state; + const top = React.cloneElement(this.props.children[0], {height: topHeight}); + const bottom = React.cloneElement(this.props.children[1], {height: bottomHeight, top: topHeight}); + const handle = ; return (
    - {left} + {top} {handle} - {right} + {bottom}
    ); }, diff --git a/ui/src/shared/components/ResizeHandle.js b/ui/src/shared/components/ResizeHandle.js index e1c0a54ec..da51e2358 100644 --- a/ui/src/shared/components/ResizeHandle.js +++ b/ui/src/shared/components/ResizeHandle.js @@ -1,15 +1,24 @@ import React from 'react'; import cx from 'classnames'; -const {func, bool} = React.PropTypes; +const {func, bool, string} = React.PropTypes; const ResizeHandle = React.createClass({ propTypes: { onHandleStartDrag: func.isRequired, isDragging: bool.isRequired, + top: string, }, render() { - return
    ; + const {isDragging, onHandleStartDrag, top} = this.props; + + return ( +
    + ); }, }); diff --git a/ui/src/style/components/resizer.scss b/ui/src/style/components/resizer.scss index 245622977..1822ba1c9 100644 --- a/ui/src/style/components/resizer.scss +++ b/ui/src/style/components/resizer.scss @@ -10,12 +10,12 @@ $resizer-color-hover: $g8-storm; $resizer-color-active: $c-pool; .resizer__handle { - top: 0; + top: 60%; left: 0; - width: $resizer-click-area; - margin-left: -$resizer-click-area/2; - margin-right: -$resizer-click-area/2; - height: 100%; + height: $resizer-click-area; + margin-top: -$resizer-click-area/2; + margin-bottom: -$resizer-click-area/2; + width: 100%; z-index: 2; user-select: none; -webkit-user-select: none; @@ -31,7 +31,7 @@ $resizer-color-active: $c-pool; position: absolute; top: 50%; left: 50%; - transform: translate(-50%,-50%) rotate(90deg); + transform: translate(-50%,-50%); width: 130px; height: $resizer-handle-width; line-height: $resizer-handle-width; @@ -48,18 +48,18 @@ $resizer-color-active: $c-pool; content: ''; display: block; position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - height: 100%; - width: $resizer-line-width; + top: 50%; + left: 0; + transform: translateY(-50%); + width: 100%; + height: $resizer-line-width; background-color: $resizer-color; box-shadow: 0 0 0 transparent; transition: background-color 0.19s ease; } &:hover { - cursor: ew-resize; + cursor: ns-resize; &:before { background-color: $resizer-color-hover; @@ -85,4 +85,4 @@ $resizer-color-active: $c-pool; display: flex; flex-direction: row; align-items: stretch; -} \ No newline at end of file +} diff --git a/ui/src/style/pages/data-explorer.scss b/ui/src/style/pages/data-explorer.scss index 7a60c4e56..ae420deee 100644 --- a/ui/src/style/pages/data-explorer.scss +++ b/ui/src/style/pages/data-explorer.scss @@ -25,6 +25,16 @@ } } +$query-editor-gutter: 16px; +$query-editor-tab-inactive: $g2-kevlar; +$query-editor-tab-active: $g3-castle; +$query-editor-height: 250px; +$graph-bg-color: $g3-castle; +$graph-active-color: $g4-onyx; +$graph-radius: 4px; +$de-vertical-margin: 16px; +$dygraphs-legend-offset: 32px; +$de-graph-heading-height: 44px; // DE Specific components @import 'data-explorer/query-builder'; diff --git a/ui/src/style/pages/data-explorer/query-builder.scss b/ui/src/style/pages/data-explorer/query-builder.scss index ec7b98317..185b337d6 100644 --- a/ui/src/style/pages/data-explorer/query-builder.scss +++ b/ui/src/style/pages/data-explorer/query-builder.scss @@ -2,10 +2,8 @@ position: absolute; width: calc(100% - #{($explorer-page-padding * 2)}); left: $explorer-page-padding; - height: calc(40% - 16px); + height: 40%; top: 60%; - background-color: $g3-castle; - border-radius: $radius; border: 0; display: flex; align-items: stretch; @@ -16,6 +14,8 @@ .query-builder--tabs { display: flex; width: 250px; + margin-top: $de-vertical-margin; + height: calc(100% - #{($de-vertical-margin * 2)}); flex-direction: column; align-items: stretch; @include gradient-v($g3-castle,$g1-raven); @@ -144,6 +144,8 @@ $query-builder--column-heading-height: 60px; .query-builder--tab-contents { width: 100%; + margin-top: $de-vertical-margin; + height: calc(100% - #{($de-vertical-margin * 2)}); background-color: $g4-onyx; border-radius: 0 $radius $radius 0; overflow: hidden; diff --git a/ui/src/style/pages/data-explorer/query-editor.scss b/ui/src/style/pages/data-explorer/query-editor.scss index 729086039..5891466da 100644 --- a/ui/src/style/pages/data-explorer/query-editor.scss +++ b/ui/src/style/pages/data-explorer/query-editor.scss @@ -5,10 +5,6 @@ Abbreviated as "qeditor" */ -$query-editor-gutter: 16px; -$query-editor-tab-inactive: $g2-kevlar; -$query-editor-tab-active: $g3-castle; -$query-editor-height: 250px; .query-builder--query-preview { position: relative; diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index 7db5b62e3..6ee1e69c1 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -1,23 +1,19 @@ -$graph-bg-color: $g3-castle; -$graph-active-color: $g4-onyx; -$graph-radius: 4px; -$de-vertical-margin: 16px; -$dygraphs-legend-offset: 32px; - .graph { position: absolute; width: calc(100% - #{($explorer-page-padding * 2)}); left: $explorer-page-padding; - top: $de-vertical-margin; + top: 0; height: 60%; } .graph-heading { + position: relative; + top: $de-vertical-margin; background-color: $graph-bg-color; border-radius: $graph-radius $graph-radius 0 0; display: flex; align-items: center; justify-content: space-between; - height: 44px; + height: $de-graph-heading-height; padding: 0 16px; transition: background-color 0.25s ease; @@ -48,7 +44,8 @@ $dygraphs-legend-offset: 32px; background-color 0.25s ease; } .data-explorer .graph-container { - height: calc(100% - 44px - 32px); + top: $de-vertical-margin; + height: calc(100% - #{$de-graph-heading-height} - #{($de-vertical-margin * 2)}); padding: 0; & > div { From 780c6812ab5876718819d722b20063e624155d47 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 13:55:12 -0800 Subject: [PATCH 21/30] DE Graph Labels cannot be selected --- ui/src/style/components/dygraphs.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index 16301b225..991c3a9e6 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -82,21 +82,25 @@ .dygraph-axis-label { color: $g11-sidewalk !important; font-weight: 500 !important; + user-select: none; } .dygraph-axis-label-y { padding: 0 9px 0 0 !important; text-align: left !important; left: 0 !important; + user-select: none; } .dygraph-axis-label-y2 { padding: 0 0 0 9px !important; text-align: right !important; + user-select: none; } .graph-container > div > div > div > div {} /* Vertical Axis Labels */ .dygraph-ylabel, .dygraph-y2label { + user-select: none; position: absolute; width: 100%; text-align: center; From f37bed54032eded51e0e8ad8fa1cb30f74a5dfd2 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 17:13:34 -0600 Subject: [PATCH 22/30] Fix url_prefixer to write asset headers --- server/url_prefixer.go | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/server/url_prefixer.go b/server/url_prefixer.go index 54e2a35f8..d20563fc1 100644 --- a/server/url_prefixer.go +++ b/server/url_prefixer.go @@ -22,16 +22,27 @@ type wrapResponseWriter struct { Substitute *io.PipeWriter headerWritten bool - dupHeader http.Header + dupHeader *http.Header } -func (wrw wrapResponseWriter) Write(p []byte) (int, error) { +func (wrw *wrapResponseWriter) Write(p []byte) (int, error) { return wrw.Substitute.Write(p) } -func (wrw wrapResponseWriter) WriteHeader(code int) { +func (wrw *wrapResponseWriter) WriteHeader(code int) { if !wrw.headerWritten { - wrw.ResponseWriter.Header().Set("Content-Type", wrw.Header().Get("Content-Type")) + wrw.ResponseWriter.Header().Set("Content-Type", wrw.dupHeader.Get("Content-Type")) + header := wrw.ResponseWriter.Header() + // Filter out content length header to prevent stopping writing + if wrw.dupHeader != nil { + for k, v := range *wrw.dupHeader { + if k == "Content-Length" { + continue + } + header[k] = v + } + } + wrw.headerWritten = true } wrw.ResponseWriter.WriteHeader(code) @@ -39,13 +50,16 @@ func (wrw wrapResponseWriter) WriteHeader(code int) { // Header() copies the Header map from the underlying ResponseWriter to prevent // modifications to it by callers -func (wrw wrapResponseWriter) Header() http.Header { - wrw.dupHeader = http.Header{} - origHeader := wrw.ResponseWriter.Header() - for k, v := range origHeader { - wrw.dupHeader[k] = v +func (wrw *wrapResponseWriter) Header() http.Header { + if wrw.dupHeader == nil { + h := http.Header{} + origHeader := wrw.ResponseWriter.Header() + for k, v := range origHeader { + h[k] = v + } + wrw.dupHeader = &h } - return wrw.dupHeader + return *wrw.dupHeader } const CHUNK_SIZE int = 512 @@ -73,7 +87,7 @@ func (up *URLPrefixer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { nextRead, nextWrite := io.Pipe() go func() { defer nextWrite.Close() - up.Next.ServeHTTP(wrapResponseWriter{ResponseWriter: rw, Substitute: nextWrite}, r) + up.Next.ServeHTTP(&wrapResponseWriter{ResponseWriter: rw, Substitute: nextWrite}, r) }() // setup a buffer which is the max length of our target attrs From 2c36a53d31fe62795e2061594ca6393d15784585 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 15:20:39 -0800 Subject: [PATCH 23/30] Fix Table view in DE graph --- ui/src/data_explorer/components/MultiTable.js | 13 +++++-- ui/src/data_explorer/components/Table.js | 24 +++++++++---- .../data_explorer/components/Visualization.js | 7 ++-- ui/src/shared/components/ResizeContainer.js | 11 +++--- .../pages/data-explorer/visualization.scss | 35 +++++++++++++++++++ 5 files changed, 74 insertions(+), 16 deletions(-) diff --git a/ui/src/data_explorer/components/MultiTable.js b/ui/src/data_explorer/components/MultiTable.js index aa12fb7fb..3fe57d310 100644 --- a/ui/src/data_explorer/components/MultiTable.js +++ b/ui/src/data_explorer/components/MultiTable.js @@ -2,7 +2,14 @@ import React, {PropTypes} from 'react'; import Table from './Table'; import classNames from 'classnames'; -const {bool, string, shape, arrayOf, func} = PropTypes; +const { + arrayOf, + bool, + func, + number, + shape, + string, +} = PropTypes; const MultiTable = React.createClass({ propTypes: { @@ -10,6 +17,7 @@ const MultiTable = React.createClass({ host: arrayOf(string.isRequired).isRequired, text: string.isRequired, })), + height: number, }, getInitialState() { @@ -40,13 +48,14 @@ const MultiTable = React.createClass({ }, renderTable() { + const {height} = this.props; const query = this.getActiveQuery(); const noQuery = !query || !query.text; if (noQuery) { return null; } - return ; + return
    ; }, renderTabs() { diff --git a/ui/src/data_explorer/components/Table.js b/ui/src/data_explorer/components/Table.js index 12f71084f..e236cf647 100644 --- a/ui/src/data_explorer/components/Table.js +++ b/ui/src/data_explorer/components/Table.js @@ -33,6 +33,7 @@ const ChronoTable = React.createClass({ text: string.isRequired, }), containerWidth: number.isRequired, + height: number, }, getInitialState() { @@ -45,6 +46,12 @@ const ChronoTable = React.createClass({ }; }, + getDefaultProps() { + return { + height: 600, + }; + }, + fetchCellData(query) { this.setState({isLoading: true}); // second param is db, we want to leave this blank @@ -81,30 +88,33 @@ const ChronoTable = React.createClass({ // Table data as a list of array. render() { - const {containerWidth} = this.props; + const {containerWidth, height} = this.props; const {cellData, columnWidths, isLoading} = this.state; const {columns, values} = cellData; - const ownerHeight = 300; + // adjust height to proper value by subtracting the heights of the UI around it + // tab height, graph-container vertical padding, graph-heading height, multitable-header height + const stylePixelOffset = 136; + const rowHeight = 34; - const height = 300; const width = 200; - const headerHeight = 40; + const headerHeight = 30; const minWidth = 70; + const styleAdjustedHeight = height - stylePixelOffset; if (!isLoading && !values.length) { - return
    Your query returned no data
    ; + return
    Your query returned no data
    ; } return (
    {columns.map((columnName, colIndex) => { return ( diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 7c726d77c..d2f94d99a 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -23,6 +23,7 @@ const Visualization = React.createClass({ name: string, activeQueryIndex: number, height: string, + heightPixels: number, }, contextTypes: { @@ -44,7 +45,7 @@ const Visualization = React.createClass({ }, render() { - const {queryConfigs, timeRange, activeQueryIndex, height} = this.props; + const {queryConfigs, timeRange, activeQueryIndex, height, heightPixels} = this.props; const {source} = this.context; const proxyLink = source.links.proxy; @@ -72,7 +73,7 @@ const Visualization = React.createClass({ -
    +
    {isGraphInView ? ( - ) : } + ) : }
    ); diff --git a/ui/src/shared/components/ResizeContainer.js b/ui/src/shared/components/ResizeContainer.js index d2cc60d5b..882f72119 100644 --- a/ui/src/shared/components/ResizeContainer.js +++ b/ui/src/shared/components/ResizeContainer.js @@ -48,16 +48,19 @@ const ResizeContainer = React.createClass({ // Don't trigger a resize if the new sizes are too small const minTopPanelHeight = 300; const minBottomPanelHeight = 250; - if (((newTopPanelPercent / turnToPercent) * appHeight) < minTopPanelHeight || ((newBottomPanelPercent / turnToPercent) * appHeight) < minBottomPanelHeight) { + const topHeightPixels = ((newTopPanelPercent / turnToPercent) * appHeight); + const bottomHeightPixels = ((newBottomPanelPercent / turnToPercent) * appHeight); + + if (topHeightPixels < minTopPanelHeight || bottomHeightPixels < minBottomPanelHeight) { return; } - this.setState({topHeight: `${(newTopPanelPercent)}%`, bottomHeight: `${(newBottomPanelPercent)}%`}); + this.setState({topHeight: `${(newTopPanelPercent)}%`, bottomHeight: `${(newBottomPanelPercent)}%`, topHeightPixels}); }, render() { - const {topHeight, bottomHeight, isDragging} = this.state; - const top = React.cloneElement(this.props.children[0], {height: topHeight}); + const {topHeight, bottomHeight, isDragging, topHeightPixels} = this.state; + const top = React.cloneElement(this.props.children[0], {height: topHeight, heightPixels: topHeightPixels}); const bottom = React.cloneElement(this.props.children[1], {height: bottomHeight, top: topHeight}); const handle = ; diff --git a/ui/src/style/pages/data-explorer/visualization.scss b/ui/src/style/pages/data-explorer/visualization.scss index 6ee1e69c1..a6bb70e95 100644 --- a/ui/src/style/pages/data-explorer/visualization.scss +++ b/ui/src/style/pages/data-explorer/visualization.scss @@ -34,6 +34,41 @@ display: flex; align-items: center; } +.table-container { + background-color: $graph-bg-color; + border-radius: 0 0 $graph-radius $graph-radius; + padding: 8px 16px; + position: relative; + top: $de-vertical-margin; + height: calc(100% - #{$de-graph-heading-height} - #{($de-vertical-margin * 2)}); + + & > div { + position: absolute; + width: calc(100% - #{($de-vertical-margin * 2)}); + height: calc(100% - #{$de-vertical-margin}); + top: ($de-vertical-margin/2); + left: $de-vertical-margin;; + } + & > div .multi-table__tabs { + position: absolute; + height: 30px; + width: 100%; + } + & > div > div:last-child { + position: absolute; + top: 30px; + height: calc(100% - 30px) !important; + width: 100%; + } + .fixedDataTableLayout_main { + height: 100% !important; + } + .generic-empty-state { + background-color: $g6-smoke; + padding: 50px 0; + height: 100%; + } +} .graph-container { background-color: $graph-bg-color; border-radius: 0 0 $graph-radius $graph-radius; From 364d2f0b92ac1709fb21a4e4f48cc37cdcae21f9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Feb 2017 15:23:09 -0800 Subject: [PATCH 24/30] Fix height of InfluxQL editor --- ui/src/style/pages/data-explorer/raw-text.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/style/pages/data-explorer/raw-text.scss b/ui/src/style/pages/data-explorer/raw-text.scss index 7157001e6..441992472 100644 --- a/ui/src/style/pages/data-explorer/raw-text.scss +++ b/ui/src/style/pages/data-explorer/raw-text.scss @@ -26,12 +26,12 @@ $raw-text-color: $c-comet; @include custom-scrollbar($g2-kevlar, $raw-text-color); display: block; width: 100%; - height: 100px; + height: 56px; background-color: $g2-kevlar; border: 2px solid $g2-kevlar; color: $raw-text-color; padding: (9px - 2px); - border-radius: 3px 3px 0 0; + border-radius: $radius; line-height: 1.5em; -webkit-appearance: none; -moz-appearance: none; From 2aa35d877191ebc06df84bb93c58f12fa76a46b8 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 17:25:58 -0600 Subject: [PATCH 25/30] Add server cache-control and etags for production assets --- dist/dist.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dist/dist.go b/dist/dist.go index 07b9d0ab4..ba3d3c938 100644 --- a/dist/dist.go +++ b/dist/dist.go @@ -3,6 +3,7 @@ package dist //go:generate go-bindata -o dist_gen.go -ignore 'map|go' -pkg dist ../ui/build/... import ( + "fmt" "net/http" "github.com/elazarl/go-bindata-assetfs" @@ -32,6 +33,21 @@ func (b *BindataAssets) Handler() http.Handler { return b } +// addCacheHeaders requests an hour of Cache-Control and sets an ETag based on file size and modtime +func (b *BindataAssets) addCacheHeaders(filename string, w http.ResponseWriter) error { + w.Header().Add("Cache-Control", "public, max-age=3600") + fi, err := AssetInfo(filename) + if err != nil { + return err + } + + hour, minute, second := fi.ModTime().Clock() + etag := fmt.Sprintf(`"%d%d%d%d%d"`, fi.Size(), fi.ModTime().Day(), hour, minute, second) + + w.Header().Set("ETag", etag) + return nil +} + // ServeHTTP wraps http.FileServer by returning a default asset if the asset // doesn't exist. This supports single-page react-apps with its own // built-in router. Additionally, we override the content-type if the @@ -52,8 +68,14 @@ func (b *BindataAssets) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Additionally, because we know we are returning the default asset, // we need to set the default asset's content-type. w.Header().Set("Content-Type", b.DefaultContentType) + if err := b.addCacheHeaders(b.Default, w); err != nil { + return nil, err + } return Asset(b.Default) } + if err := b.addCacheHeaders(name, w); err != nil { + return nil, err + } return octets, nil } var dir http.FileSystem = &assetfs.AssetFS{ From 1f4488b0e584189970dfe9d7326db7bd51460c39 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 17:30:37 -0600 Subject: [PATCH 26/30] Update CHANGELOG to mention server gzip encoding and asset caching --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f072a7385..b81b4a635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Upcoming Features 1. [#838](https://github.com/influxdata/chronograf/issues/838): Add detail node to kapacitor alerts 2. [#853](https://github.com/influxdata/chronograf/issues/853): Updated builds to use yarn over npm install + 3. [#860](https://github.com/influxdata/chronograf/issues/860): Add gzip encoding and caching of static assets to server ### Upcoming UI Improvements From dac665b944e315f2a17f10e7281854289e29e83f Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 10 Feb 2017 08:47:08 -0600 Subject: [PATCH 27/30] Add handling of string values for kapacitor threshold alerts --- kapacitor/tickscripts_test.go | 274 ++++++++++++++++++++++++++++++++++ kapacitor/vars.go | 21 ++- kapacitor/vars_test.go | 50 +++++++ 3 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 kapacitor/vars_test.go diff --git a/kapacitor/tickscripts_test.go b/kapacitor/tickscripts_test.go index c00e04378..d201d44b8 100644 --- a/kapacitor/tickscripts_test.go +++ b/kapacitor/tickscripts_test.go @@ -199,6 +199,280 @@ trigger } } +func TestThresholdStringCrit(t *testing.T) { + alert := chronograf.AlertRule{ + Name: "haproxy", + Trigger: "threshold", + Alerts: []string{"email"}, + TriggerValues: chronograf.TriggerValues{ + Operator: "equal to", + Value: "DOWN", + }, + Every: "10s", + Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, + Details: "Email template", + Query: chronograf.QueryConfig{ + Database: "influxdb", + RetentionPolicy: "autogen", + Measurement: "haproxy", + Fields: []chronograf.Field{ + { + Field: "status", + Funcs: []string{"last"}, + }, + }, + GroupBy: chronograf.GroupBy{ + Time: "10s", + Tags: []string{"pxname"}, + }, + AreTagsAccepted: true, + }, + } + + tests := []struct { + name string + alert chronograf.AlertRule + want chronograf.TICKScript + wantErr bool + }{ + { + name: "Test valid template alert", + alert: alert, + want: `var db = 'influxdb' + +var rp = 'autogen' + +var measurement = 'haproxy' + +var groupBy = ['pxname'] + +var whereFilter = lambda: TRUE + +var period = 10s + +var every = 10s + +var name = 'haproxy' + +var idVar = name + ':{{.Group}}' + +var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' + +var idTag = 'alertID' + +var levelTag = 'level' + +var messageField = 'message' + +var durationField = 'duration' + +var outputDB = 'chronograf' + +var outputRP = 'autogen' + +var outputMeasurement = 'alerts' + +var triggerType = 'threshold' + +var details = 'Email template' + +var crit = 'DOWN' + +var data = stream + |from() + .database(db) + .retentionPolicy(rp) + .measurement(measurement) + .groupBy(groupBy) + .where(whereFilter) + |window() + .period(period) + .every(every) + .align() + |last('status') + .as('value') + +var trigger = data + |alert() + .crit(lambda: "value" == crit) + .stateChangesOnly() + .message(message) + .id(idVar) + .idTag(idTag) + .levelTag(levelTag) + .messageField(messageField) + .durationField(durationField) + .details(details) + .email() + +trigger + |influxDBOut() + .create() + .database(outputDB) + .retentionPolicy(outputRP) + .measurement(outputMeasurement) + .tag('alertName', name) + .tag('triggerType', triggerType) + +trigger + |httpOut('output') +`, + wantErr: false, + }, + } + for _, tt := range tests { + gen := Alert{} + got, err := gen.Generate(tt.alert) + if (err != nil) != tt.wantErr { + t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if got != tt.want { + diff := diffmatchpatch.New() + delta := diff.DiffMain(string(tt.want), string(got), true) + t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) + } + } +} + +// TODO: Check with Nathaniel if kapacitor can do inequalities on strings +// If it cannot, I think we should add operator checks. +func TestThresholdStringCritGreater(t *testing.T) { + alert := chronograf.AlertRule{ + Name: "haproxy", + Trigger: "threshold", + Alerts: []string{"email"}, + TriggerValues: chronograf.TriggerValues{ + Operator: "greater than", + Value: "DOWN", + }, + Every: "10s", + Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, + Details: "Email template", + Query: chronograf.QueryConfig{ + Database: "influxdb", + RetentionPolicy: "autogen", + Measurement: "haproxy", + Fields: []chronograf.Field{ + { + Field: "status", + Funcs: []string{"last"}, + }, + }, + GroupBy: chronograf.GroupBy{ + Time: "10s", + Tags: []string{"pxname"}, + }, + AreTagsAccepted: true, + }, + } + + tests := []struct { + name string + alert chronograf.AlertRule + want chronograf.TICKScript + wantErr bool + }{ + { + name: "Test valid template alert", + alert: alert, + want: `var db = 'influxdb' + +var rp = 'autogen' + +var measurement = 'haproxy' + +var groupBy = ['pxname'] + +var whereFilter = lambda: TRUE + +var period = 10s + +var every = 10s + +var name = 'haproxy' + +var idVar = name + ':{{.Group}}' + +var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' + +var idTag = 'alertID' + +var levelTag = 'level' + +var messageField = 'message' + +var durationField = 'duration' + +var outputDB = 'chronograf' + +var outputRP = 'autogen' + +var outputMeasurement = 'alerts' + +var triggerType = 'threshold' + +var details = 'Email template' + +var crit = 'DOWN' + +var data = stream + |from() + .database(db) + .retentionPolicy(rp) + .measurement(measurement) + .groupBy(groupBy) + .where(whereFilter) + |window() + .period(period) + .every(every) + .align() + |last('status') + .as('value') + +var trigger = data + |alert() + .crit(lambda: "value" > crit) + .stateChangesOnly() + .message(message) + .id(idVar) + .idTag(idTag) + .levelTag(levelTag) + .messageField(messageField) + .durationField(durationField) + .details(details) + .email() + +trigger + |influxDBOut() + .create() + .database(outputDB) + .retentionPolicy(outputRP) + .measurement(outputMeasurement) + .tag('alertName', name) + .tag('triggerType', triggerType) + +trigger + |httpOut('output') +`, + wantErr: false, + }, + } + for _, tt := range tests { + gen := Alert{} + got, err := gen.Generate(tt.alert) + if (err != nil) != tt.wantErr { + t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if got != tt.want { + diff := diffmatchpatch.New() + delta := diff.DiffMain(string(tt.want), string(got), true) + t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) + } + } +} + func TestThresholdDetail(t *testing.T) { alert := chronograf.AlertRule{ Name: "name", diff --git a/kapacitor/vars.go b/kapacitor/vars.go index f9239e6bf..0c1cfd147 100644 --- a/kapacitor/vars.go +++ b/kapacitor/vars.go @@ -3,6 +3,7 @@ package kapacitor import ( "fmt" "sort" + "strconv" "strings" "github.com/influxdata/chronograf" @@ -39,15 +40,16 @@ func Vars(rule chronograf.AlertRule) (string, error) { %s var crit = %s ` - return fmt.Sprintf(vars, - common, - rule.TriggerValues.Value), nil + // If critical value is a string, we'll + // need to single-quote it. + crit := critVar(rule.TriggerValues.Value) + return fmt.Sprintf(vars, common, crit), nil } else { vars := ` %s var lower = %s var upper = %s - ` +` return fmt.Sprintf(vars, common, rule.TriggerValues.Value, @@ -178,3 +180,14 @@ func whereFilter(q chronograf.QueryConfig) string { return "lambda: TRUE" } + +// critVar return the same string if a numeric type +// or if it is a string will return it as a kapacitor +// formatted single-quoted string +func critVar(value string) string { + // Test if numeric if it can be converted to a float + if _, err := strconv.ParseFloat(value, 64); err == nil { + return value + } + return "'" + value + "'" +} diff --git a/kapacitor/vars_test.go b/kapacitor/vars_test.go new file mode 100644 index 000000000..e19104aef --- /dev/null +++ b/kapacitor/vars_test.go @@ -0,0 +1,50 @@ +package kapacitor + +import ( + "fmt" + "testing" + + "github.com/influxdata/chronograf" +) + +func TestVarsCritStringEqual(t *testing.T) { + alert := chronograf.AlertRule{ + Name: "name", + Trigger: "threshold", + TriggerValues: chronograf.TriggerValues{ + Operator: "equal to", + Value: "DOWN", + }, + Every: "30s", + Query: chronograf.QueryConfig{ + Database: "telegraf", + Measurement: "haproxy", + RetentionPolicy: "autogen", + Fields: []chronograf.Field{ + { + Field: "status", + }, + }, + GroupBy: chronograf.GroupBy{ + Time: "10m", + Tags: []string{"pxname"}, + }, + AreTagsAccepted: true, + }, + } + + raw, err := Vars(alert) + if err != nil { + fmt.Printf("%s", raw) + t.Fatalf("Error generating alert: %v %s", err, raw) + } + + tick, err := formatTick(raw) + if err != nil { + t.Errorf("Error formatting alert: %v %s", err, raw) + } + + if err := validateTick(tick); err != nil { + t.Errorf("Error validating alert: %v %s", err, tick) + } +} From 34c6dd20741449e60787d78b66f0eaecd27ba2da Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 10 Feb 2017 09:12:00 -0600 Subject: [PATCH 28/30] Update CHANGELOG mentioning string fields compare kapacitor rules in Chronograf UI --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f072a7385..6f9cfd736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## v1.2.0 [unreleased] ### Upcoming Bug Fixes + 1. [#865](https://github.com/influxdata/chronograf/issues/865): Support for String fields compare kapacitor rules in Chronograf UI ### Upcoming Features 1. [#838](https://github.com/influxdata/chronograf/issues/838): Add detail node to kapacitor alerts From febc93a2c030b7b3004c18022e2065f6e27a33dd Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 10 Feb 2017 10:06:26 -0600 Subject: [PATCH 29/30] Update function name for kapacitor's formatting of values --- kapacitor/vars.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/kapacitor/vars.go b/kapacitor/vars.go index 0c1cfd147..081f349fb 100644 --- a/kapacitor/vars.go +++ b/kapacitor/vars.go @@ -40,10 +40,7 @@ func Vars(rule chronograf.AlertRule) (string, error) { %s var crit = %s ` - // If critical value is a string, we'll - // need to single-quote it. - crit := critVar(rule.TriggerValues.Value) - return fmt.Sprintf(vars, common, crit), nil + return fmt.Sprintf(vars, common, formatValue(rule.TriggerValues.Value)), nil } else { vars := ` %s @@ -181,10 +178,9 @@ func whereFilter(q chronograf.QueryConfig) string { return "lambda: TRUE" } -// critVar return the same string if a numeric type -// or if it is a string will return it as a kapacitor -// formatted single-quoted string -func critVar(value string) string { +// formatValue return the same string if a numeric type or if it is a string +// will return it as a kapacitor formatted single-quoted string +func formatValue(value string) string { // Test if numeric if it can be converted to a float if _, err := strconv.ParseFloat(value, 64); err == nil { return value From 602725a06aaad203b1421524d810a29fc029dfed Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 10 Feb 2017 08:20:11 -0800 Subject: [PATCH 30/30] Adjust min heights builder and visualizations --- ui/src/shared/components/ResizeContainer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/shared/components/ResizeContainer.js b/ui/src/shared/components/ResizeContainer.js index 882f72119..5b30257ba 100644 --- a/ui/src/shared/components/ResizeContainer.js +++ b/ui/src/shared/components/ResizeContainer.js @@ -46,8 +46,8 @@ const ResizeContainer = React.createClass({ } // Don't trigger a resize if the new sizes are too small - const minTopPanelHeight = 300; - const minBottomPanelHeight = 250; + const minTopPanelHeight = 200; + const minBottomPanelHeight = 100; const topHeightPixels = ((newTopPanelPercent / turnToPercent) * appHeight); const bottomHeightPixels = ((newBottomPanelPercent / turnToPercent) * appHeight);