diff --git a/.gitignore b/.gitignore
index bbef07c3ad..b580642afd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ npm-debug.log
.jssrc
.dev-jssrc
.bindata
+ui/reports
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6364618d0..47d66fbbdc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,14 @@
## v1.3.7.0 [unreleased]
### Bug Fixes
+1. [#1795](https://github.com/influxdata/chronograf/pull/1795): Fix uptime status on Windows hosts running Telegraf
+1. [#1715](https://github.com/influxdata/chronograf/pull/1715): Chronograf now renders on IE11.
1. [#1845](https://github.com/influxdata/chronograf/pull/1845): Fix no-scroll bar appearing in the Data Explorer table
1. [#1870](https://github.com/influxdata/chronograf/pull/1870): Fix console error for placing prop on div
+1. [#1866](https://github.com/influxdata/chronograf/pull/1866): Fix missing cell type (and consequently single-stat)
+
### Features
+1. [#1863](https://github.com/influxdata/chronograf/pull/1863): Improve 'new-sources' server flag example by adding 'type' key
### UI Improvements
1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay
diff --git a/chronograf.go b/chronograf.go
index 319065befc..aa9c6bc6c9 100644
--- a/chronograf.go
+++ b/chronograf.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
+ "regexp"
"strconv"
"strings"
"time"
@@ -193,32 +194,103 @@ type GroupByVar struct {
// Exec is responsible for extracting the Duration from the query
func (g *GroupByVar) Exec(query string) {
- whereClause := "WHERE time > now() - "
+ whereClause := "WHERE"
start := strings.Index(query, whereClause)
if start == -1 {
// no where clause
return
}
- // reposition start to the END of the where clause
+ // reposition start to after the 'where' keyword
durStr := query[start+len(whereClause):]
- // advance to next space
+ // attempt to parse out a relative time range
+ dur, err := g.parseRelative(durStr)
+ if err == nil {
+ // we parsed relative duration successfully
+ g.Duration = dur
+ return
+ }
+
+ dur, err = g.parseAbsolute(durStr)
+ if err == nil {
+ // we found an absolute time range
+ g.Duration = dur
+ }
+}
+
+// parseRelative locates and extracts a duration value from a fragment of an
+// InfluxQL query following the "where" keyword. For example, in the fragment
+// "time > now() - 180d GROUP BY :interval:", parseRelative would return a
+// duration equal to 180d
+func (g *GroupByVar) parseRelative(fragment string) (time.Duration, error) {
+ // locate duration literal start
+ prefix := "time > now() - "
+ start := strings.Index(fragment, prefix)
+ if start == -1 {
+ return time.Duration(0), errors.New("not a relative duration")
+ }
+
+ // reposition to duration literal
+ durFragment := fragment[start+len(prefix):]
+
+ // init counters
pos := 0
- for pos < len(durStr) {
- rn, _ := utf8.DecodeRuneInString(durStr[pos:])
+
+ // locate end of duration literal
+ for pos < len(durFragment) {
+ rn, _ := utf8.DecodeRuneInString(durFragment[pos:])
if unicode.IsSpace(rn) {
break
}
pos++
}
- dur, err := influxql.ParseDuration(durStr[:pos])
+ // attempt to parse what we suspect is a duration literal
+ dur, err := influxql.ParseDuration(durFragment[:pos])
if err != nil {
- return
+ return dur, err
}
- g.Duration = dur
+ return dur, nil
+}
+
+// parseAbsolute will determine the duration between two absolute timestamps
+// found within an InfluxQL fragment following the "where" keyword. For
+// example, the fragement "time > '1985-10-25T00:01:21-0800 and time <
+// '1985-10-25T00:01:22-0800'" would yield a duration of 1m'
+func (g *GroupByVar) parseAbsolute(fragment string) (time.Duration, error) {
+ timePtn := `time\s[>|<]\s'([0-9\-TZ\:]+)'` // Playground: http://gobular.com/x/41a45095-c384-46ea-b73c-54ef91ab93af
+ re, err := regexp.Compile(timePtn)
+ if err != nil {
+ // this is a developer error and should complain loudly
+ panic("Bad Regex: err:" + err.Error())
+ }
+
+ if !re.Match([]byte(fragment)) {
+ return time.Duration(0), errors.New("absolute duration not found")
+ }
+
+ // extract at most two times
+ matches := re.FindAll([]byte(fragment), 2)
+
+ // parse out absolute times
+ durs := make([]time.Time, 0, 2)
+ for _, match := range matches {
+ durStr := re.FindSubmatch(match)
+ if tm, err := time.Parse(time.RFC3339Nano, string(durStr[1])); err == nil {
+ durs = append(durs, tm)
+ }
+ }
+
+ // reject more than 2 times found
+ if len(durs) != 2 {
+ return time.Duration(0), errors.New("must provide exactly two absolute times")
+ }
+
+ dur := durs[1].Sub(durs[0])
+
+ return dur, nil
}
func (g *GroupByVar) String() string {
diff --git a/chronograf_test.go b/chronograf_test.go
new file mode 100644
index 0000000000..60164d5880
--- /dev/null
+++ b/chronograf_test.go
@@ -0,0 +1,49 @@
+package chronograf_test
+
+import (
+ "testing"
+ "time"
+
+ "github.com/influxdata/chronograf"
+)
+
+func Test_GroupByVar(t *testing.T) {
+ gbvTests := []struct {
+ name string
+ query string
+ expected time.Duration
+ resolution uint // the screen resolution to render queries into
+ reportingInterval time.Duration
+ }{
+ {
+ "relative time",
+ "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 180d GROUP BY :interval:",
+ 4320 * time.Hour,
+ 1000,
+ 10 * time.Second,
+ },
+ {
+ "absolute time",
+ "SELECT mean(usage_idle) FROM cpu WHERE time > '1985-10-25T00:01:00Z' and time < '1985-10-25T00:02:00Z' GROUP BY :interval:",
+ 1 * time.Minute,
+ 1000,
+ 10 * time.Second,
+ },
+ }
+
+ for _, test := range gbvTests {
+ t.Run(test.name, func(t *testing.T) {
+ gbv := chronograf.GroupByVar{
+ Var: ":interval:",
+ Resolution: test.resolution,
+ ReportingInterval: test.reportingInterval,
+ }
+
+ gbv.Exec(test.query)
+
+ if gbv.Duration != test.expected {
+ t.Fatalf("%q - durations not equal! Want: %s, Got: %s", test.name, test.expected, gbv.Duration)
+ }
+ })
+ }
+}
diff --git a/server/cells.go b/server/cells.go
index 29c0bd57a2..29d78b000d 100644
--- a/server/cells.go
+++ b/server/cells.go
@@ -45,6 +45,7 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
newCell.H = cell.H
newCell.Name = cell.Name
newCell.ID = cell.ID
+ newCell.Type = cell.Type
for _, lbl := range labels {
if axis, found := cell.Axes[lbl]; !found {
diff --git a/server/cells_test.go b/server/cells_test.go
index 5ca5dce5e1..c0ade6f5f7 100644
--- a/server/cells_test.go
+++ b/server/cells_test.go
@@ -105,6 +105,7 @@ func Test_Service_DashboardCells(t *testing.T) {
W: 4,
H: 4,
Name: "CPU",
+ Type: "bar",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{},
},
@@ -117,6 +118,7 @@ func Test_Service_DashboardCells(t *testing.T) {
W: 4,
H: 4,
Name: "CPU",
+ Type: "bar",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
diff --git a/server/server.go b/server/server.go
index eff14cbf38..d51d8148c6 100644
--- a/server/server.go
+++ b/server/server.go
@@ -50,7 +50,7 @@ type Server struct {
KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"`
KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"`
- NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"hunter2\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
+ NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
diff --git a/ui/.eslintrc b/ui/.eslintrc
index 2dda26b78f..a9ca0ef627 100644
--- a/ui/.eslintrc
+++ b/ui/.eslintrc
@@ -102,7 +102,7 @@
'no-iterator': 2,
'no-lone-blocks': 2,
'no-loop-func': 2,
- 'no-magic-numbers': [2, {ignore: [-1, 0, 1, 2]}],
+ 'no-magic-numbers': [0, {ignore: [-1, 0, 1, 2]}],
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-native-reassign': 2,
diff --git a/ui/README.md b/ui/README.md
index 24f34aec5c..73698695b0 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -1,6 +1,8 @@
## Packages
We are using [yarn](https://yarnpkg.com/en/docs/install) 0.19.1.
+Run `yarn run` to see a list of available tasks.
+
### Adding new packages
To add a new package, run
diff --git a/ui/nightwatch.json b/ui/nightwatch.json
new file mode 100644
index 0000000000..367e9e2fe2
--- /dev/null
+++ b/ui/nightwatch.json
@@ -0,0 +1,37 @@
+{
+ "src_folders": ["tests"],
+ "output_folder": "reports",
+ "custom_commands_path": "",
+ "custom_assertions_path": "",
+ "page_objects_path": "",
+ "globals_path": "",
+
+ "selenium": {
+ "start_process": false,
+ "host": "hub-cloud.browserstack.com",
+ "port": 80
+ },
+
+ "live_output" : true,
+
+ "test_settings": {
+ "default": {
+ "selenium_port": 80,
+ "selenium_host": "hub-cloud.browserstack.com",
+ "silent": false,
+ "screenshots": {
+ "enabled": true,
+ "path": "screenshots"
+ },
+ "desiredCapabilities": {
+ "browser": "chrome",
+ "build": "nightwatch-browserstack",
+ "browserstack.user": "${BROWSERSTACK_USER}",
+ "browserstack.key": "${BROWSERSTACK_KEY}",
+ "browserstack.debug": true,
+ "browserstack.local": true,
+ "resolution": "1280x1024"
+ }
+ }
+ }
+}
diff --git a/ui/package.json b/ui/package.json
index 253f0bf1dc..6484277fb9 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -9,15 +9,16 @@
"url": "github:influxdata/chronograf"
},
"scripts": {
- "build": "yarn run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js",
- "build:dev": "node_modules/webpack/bin/webpack.js --config ./webpack/devConfig.js",
- "start": "node_modules/webpack/bin/webpack.js -w --config ./webpack/devConfig.js",
- "lint": "node_modules/eslint/bin/eslint.js src/",
+ "build": "yarn run clean && env NODE_ENV=production webpack --optimize-minimize --config ./webpack/prodConfig.js",
+ "build:dev": "webpack --config ./webpack/devConfig.js",
+ "start": "webpack --watch --config ./webpack/devConfig.js",
+ "lint": "esw src/",
"test": "karma start",
+ "test:integration": "nightwatch tests --skip",
"test:lint": "yarn run lint; yarn run test",
- "test:dev": "nodemon --exec yarn run test:lint",
+ "test:dev": "concurrently \"yarn run lint -- --watch\" \"yarn run test -- --no-single-run --reporters=verbose\"",
"clean": "rm -rf build",
- "storybook": "node ./storybook",
+ "storybook": "node ./storybook.js",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
},
"author": "",
@@ -45,14 +46,16 @@
"babel-runtime": "^6.5.0",
"bower": "^1.7.7",
"chai": "^3.5.0",
+ "concurrently": "^3.5.0",
"core-js": "^2.1.3",
"css-loader": "^0.23.1",
"envify": "^3.4.0",
"enzyme": "^2.4.1",
- "eslint": "3.9.1",
+ "eslint": "^3.14.1",
"eslint-loader": "1.6.1",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "6.6.0",
+ "eslint-watch": "^3.1.2",
"express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js
index 17ec91d3cc..b21a3a39f3 100644
--- a/ui/src/dashboards/components/CellEditorOverlay.js
+++ b/ui/src/dashboards/components/CellEditorOverlay.js
@@ -4,8 +4,8 @@ import _ from 'lodash'
import uuid from 'node-uuid'
import ResizeContainer from 'shared/components/ResizeContainer'
-import QueryMaker from 'src/data_explorer/components/QueryMaker'
-import Visualization from 'src/data_explorer/components/Visualization'
+import QueryMaker from 'src/dashboards/components/QueryMaker'
+import Visualization from 'src/dashboards/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls'
import DisplayOptions from 'src/dashboards/components/DisplayOptions'
@@ -34,6 +34,7 @@ class CellEditorOverlay extends Component {
this.handleEditRawText = ::this.handleEditRawText
this.handleSetYAxisBounds = ::this.handleSetYAxisBounds
this.handleSetLabel = ::this.handleSetLabel
+ this.getActiveQuery = ::this.getActiveQuery
const {cell: {name, type, queries, axes}} = props
@@ -111,10 +112,17 @@ class CellEditorOverlay extends Component {
e.preventDefault()
}
- handleAddQuery(options) {
- const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options)
- const nextQueries = this.state.queriesWorkingDraft.concat(newQuery)
- this.setState({queriesWorkingDraft: nextQueries})
+ handleAddQuery() {
+ const {queriesWorkingDraft} = this.state
+ const newIndex = queriesWorkingDraft.length
+
+ this.setState({
+ queriesWorkingDraft: [
+ ...queriesWorkingDraft,
+ defaultQueryConfig(uuid.v4()),
+ ],
+ })
+ this.handleSetActiveQueryIndex(newIndex)
}
handleDeleteQuery(index) {
@@ -167,6 +175,14 @@ class CellEditorOverlay extends Component {
this.setState({activeQueryIndex})
}
+ getActiveQuery() {
+ const {queriesWorkingDraft, activeQueryIndex} = this.state
+ const activeQuery = queriesWorkingDraft[activeQueryIndex]
+ const defaultQuery = queriesWorkingDraft[0]
+
+ return activeQuery || defaultQuery
+ }
+
async handleEditRawText(url, id, text) {
const templates = removeUnselectedTemplateValues(this.props.templates)
@@ -203,7 +219,6 @@ class CellEditorOverlay extends Component {
} = this.state
const queryActions = {
- addQuery: this.handleAddQuery,
editRawTextAsync: this.handleEditRawText,
..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)),
}
@@ -222,16 +237,14 @@ class CellEditorOverlay extends Component {
initialBottomHeight={INITIAL_HEIGHTS.queryMaker}
>
}
diff --git a/ui/src/dashboards/components/DashboardHeaderEdit.js b/ui/src/dashboards/components/DashboardHeaderEdit.js
index a79273a39d..f1dd5bc8a4 100644
--- a/ui/src/dashboards/components/DashboardHeaderEdit.js
+++ b/ui/src/dashboards/components/DashboardHeaderEdit.js
@@ -38,7 +38,7 @@ class DashboardEditHeader extends Component {
diff --git a/ui/src/data_explorer/components/QueryBuilder.js b/ui/src/data_explorer/components/QueryBuilder.js
deleted file mode 100644
index a7613c724c..0000000000
--- a/ui/src/data_explorer/components/QueryBuilder.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import React, {PropTypes} from 'react'
-
-import DatabaseList from './DatabaseList'
-import DatabaseDropdown from 'shared/components/DatabaseDropdown'
-import MeasurementList from './MeasurementList'
-import FieldList from './FieldList'
-import QueryEditor from './QueryEditor'
-import buildInfluxQLQuery from 'utils/influxql'
-
-const {arrayOf, bool, func, shape, string} = PropTypes
-
-const QueryBuilder = React.createClass({
- propTypes: {
- source: shape({
- links: shape({
- queries: string.isRequired,
- }).isRequired,
- }).isRequired,
- query: shape({
- id: string,
- }).isRequired,
- timeRange: shape({
- upper: string,
- lower: string,
- }).isRequired,
- templates: arrayOf(
- shape({
- tempVar: string.isRequired,
- })
- ),
- isInDataExplorer: bool,
- actions: shape({
- chooseNamespace: func.isRequired,
- chooseMeasurement: func.isRequired,
- applyFuncsToField: func.isRequired,
- chooseTag: func.isRequired,
- groupByTag: func.isRequired,
- toggleField: func.isRequired,
- groupByTime: func.isRequired,
- toggleTagAcceptance: func.isRequired,
- editRawTextAsync: func.isRequired,
- }).isRequired,
- layout: string,
- },
-
- handleChooseNamespace(namespace) {
- this.props.actions.chooseNamespace(this.props.query.id, namespace)
- },
-
- handleChooseMeasurement(measurement) {
- this.props.actions.chooseMeasurement(this.props.query.id, measurement)
- },
-
- handleToggleField(field) {
- this.props.actions.toggleFieldWithGroupByInterval(
- this.props.query.id,
- field
- )
- },
-
- handleGroupByTime(time) {
- this.props.actions.groupByTime(this.props.query.id, time)
- },
-
- handleApplyFuncsToField(fieldFunc) {
- this.props.actions.applyFuncsToField(
- this.props.query.id,
- fieldFunc,
- this.props.isInDataExplorer
- )
- },
-
- handleChooseTag(tag) {
- this.props.actions.chooseTag(this.props.query.id, tag)
- },
-
- handleToggleTagAcceptance() {
- this.props.actions.toggleTagAcceptance(this.props.query.id)
- },
-
- handleGroupByTag(tagKey) {
- this.props.actions.groupByTag(this.props.query.id, tagKey)
- },
-
- handleEditRawText(text) {
- const {source: {links}, query} = this.props
- this.props.actions.editRawTextAsync(links.queries, query.id, text)
- },
-
- render() {
- const {query, templates, isInDataExplorer} = this.props
-
- // DE does not understand templating. :dashboardTime: is specific to dashboards
- let timeRange
-
- if (isInDataExplorer) {
- timeRange = this.props.timeRange
- } else {
- timeRange = query.range || {upper: null, lower: ':dashboardTime:'}
- }
-
- const q = query.rawText || buildInfluxQLQuery(timeRange, query) || ''
-
- return (
-
-
- {this.renderLists()}
-
- )
- },
-
- renderLists() {
- const {query, layout, isInDataExplorer} = this.props
-
- // Panel layout uses a dropdown instead of a list for database selection
- // Also groups measurements & fields into their own container so they
- // can be stacked vertically.
- // TODO: Styles to make all this look proper
- if (layout === 'panel') {
- return (
-
- )
- }
-
- return (
-
-
-
-
-
- )
- },
-})
-
-export default QueryBuilder
diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js
index 4e6f49431d..cb1e7a0277 100644
--- a/ui/src/data_explorer/components/QueryEditor.js
+++ b/ui/src/data_explorer/components/QueryEditor.js
@@ -1,40 +1,20 @@
import React, {PropTypes, Component} from 'react'
-import _ from 'lodash'
-import classnames from 'classnames'
import Dropdown from 'shared/components/Dropdown'
-import LoadingDots from 'shared/components/LoadingDots'
-import TemplateDrawer from 'shared/components/TemplateDrawer'
import {QUERY_TEMPLATES} from 'src/data_explorer/constants'
-import {
- MATCH_INCOMPLETE_TEMPLATES,
- applyMasks,
- insertTempVar,
- unMask,
-} from 'src/dashboards/constants'
+import QueryStatus from 'shared/components/QueryStatus'
class QueryEditor extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.query,
- isTemplating: false,
- selectedTemplate: {
- tempVar: _.get(this.props.templates, ['0', 'tempVar'], ''),
- },
- filteredTemplates: this.props.templates,
}
this.handleKeyDown = ::this.handleKeyDown
this.handleChange = ::this.handleChange
this.handleUpdate = ::this.handleUpdate
- this.handleChooseTemplate = ::this.handleChooseTemplate
- this.handleCloseDrawer = ::this.handleCloseDrawer
- this.findTempVar = ::this.findTempVar
- this.handleTemplateReplace = ::this.handleTemplateReplace
- this.handleMouseOverTempVar = ::this.handleMouseOverTempVar
- this.handleClickTempVar = ::this.handleClickTempVar
- this.closeDrawer = ::this.closeDrawer
+ this.handleChooseMetaQuery = ::this.handleChooseMetaQuery
}
componentWillReceiveProps(nextProps) {
@@ -43,170 +23,35 @@ class QueryEditor extends Component {
}
}
- handleCloseDrawer() {
- this.setState({isTemplating: false})
- }
-
- handleMouseOverTempVar(template) {
- this.handleTemplateReplace(template)
- }
-
- handleClickTempVar(template) {
- // Clicking a tempVar does the same thing as hitting 'Enter'
- this.handleTemplateReplace(template, true)
- this.closeDrawer()
- }
-
- closeDrawer() {
- this.setState({
- isTemplating: false,
- selectedTemplate: {
- tempVar: _.get(this.props.templates, ['0', 'tempVar'], ''),
- },
- })
- }
-
handleKeyDown(e) {
- const {isTemplating, value} = this.state
+ const {value} = this.state
- if (isTemplating) {
- switch (e.key) {
- case 'Tab':
- case 'ArrowRight':
- case 'ArrowDown':
- e.preventDefault()
- return this.handleTemplateReplace(this.findTempVar('next'))
- case 'ArrowLeft':
- case 'ArrowUp':
- e.preventDefault()
- return this.handleTemplateReplace(this.findTempVar('previous'))
- case 'Enter':
- e.preventDefault()
- this.handleTemplateReplace(this.state.selectedTemplate, true)
- return this.closeDrawer()
- case 'Escape':
- e.preventDefault()
- return this.closeDrawer()
- }
- } else if (e.key === 'Escape') {
+ if (e.key === 'Escape') {
e.preventDefault()
- this.setState({value, isTemplating: false})
- } else if (e.key === 'Enter') {
+ this.setState({value})
+ }
+
+ if (e.key === 'Enter') {
e.preventDefault()
this.handleUpdate()
}
}
- handleTemplateReplace(selectedTemplate, replaceWholeTemplate) {
- const {selectionStart, value} = this.editor
- const {tempVar} = selectedTemplate
- const newTempVar = replaceWholeTemplate
- ? tempVar
- : tempVar.substring(0, tempVar.length - 1)
-
- // mask matches that will confuse our regex
- const masked = applyMasks(value)
- const matched = masked.match(MATCH_INCOMPLETE_TEMPLATES)
-
- let templatedValue
- if (matched) {
- templatedValue = insertTempVar(masked, newTempVar)
- templatedValue = unMask(templatedValue)
- }
-
- const enterModifier = replaceWholeTemplate ? 0 : -1
- const diffInLength =
- tempVar.length - _.get(matched, '0', []).length + enterModifier
-
- this.setState({value: templatedValue, selectedTemplate}, () =>
- this.editor.setSelectionRange(
- selectionStart + diffInLength,
- selectionStart + diffInLength
- )
- )
- }
-
- findTempVar(direction) {
- const {filteredTemplates: templates} = this.state
- const {selectedTemplate} = this.state
-
- const i = _.findIndex(templates, selectedTemplate)
- const lastIndex = templates.length - 1
-
- if (i >= 0) {
- if (direction === 'next') {
- return templates[(i + 1) % templates.length]
- }
-
- if (direction === 'previous') {
- if (i === 0) {
- return templates[lastIndex]
- }
-
- return templates[i - 1]
- }
- }
-
- return templates[0]
- }
-
handleChange() {
- const {templates} = this.props
- const {selectedTemplate} = this.state
- const value = this.editor.value
-
- // mask matches that will confuse our regex
- const masked = applyMasks(value)
- const matched = masked.match(MATCH_INCOMPLETE_TEMPLATES)
-
- if (matched && !_.isEmpty(templates)) {
- // maintain cursor poition
- const start = this.editor.selectionStart
-
- const end = this.editor.selectionEnd
- const filterText = matched[0].substr(1).toLowerCase()
-
- const filteredTemplates = templates.filter(t =>
- t.tempVar.toLowerCase().includes(filterText)
- )
-
- const found = filteredTemplates.find(
- t => t.tempVar === selectedTemplate && selectedTemplate.tempVar
- )
- const newTemplate = found ? found : filteredTemplates[0]
-
- this.setState({
- isTemplating: true,
- selectedTemplate: newTemplate,
- filteredTemplates,
- value,
- })
- this.editor.setSelectionRange(start, end)
- } else {
- this.setState({isTemplating: false, value})
- }
+ this.setState({value: this.editor.value})
}
handleUpdate() {
this.props.onUpdate(this.state.value)
}
- handleChooseTemplate(template) {
+ handleChooseMetaQuery(template) {
this.setState({value: template.query})
}
- handleSelectTempVar(tempVar) {
- this.setState({selectedTemplate: tempVar})
- }
-
render() {
const {config: {status}} = this.props
- const {
- value,
- isTemplating,
- selectedTemplate,
- filteredTemplates,
- } = this.state
+ const {value} = this.state
return (
@@ -220,111 +65,34 @@ class QueryEditor extends Component {
placeholder="Enter a query or select database, measurement, and field below and have us build one for you..."
autoComplete="off"
spellCheck="false"
+ data-test="query-editor-field"
/>
-
+
- {this.renderStatus(status)}
-
-
- {isTemplating
- ?
- : null}
+
+
+
)
}
-
- renderStatus(status) {
- const {isInDataExplorer} = this.props
-
- if (!status) {
- return (
-
- {isInDataExplorer
- ?
- : null}
-
- )
- }
-
- if (status.loading) {
- return (
-
-
- {isInDataExplorer
- ?
- : null}
-
- )
- }
-
- return (
-
-
-
- {status.error || status.warn || status.success}
-
- {isInDataExplorer
- ?
- : null}
-
- )
- }
}
-const {arrayOf, bool, func, shape, string} = PropTypes
+const {func, shape, string} = PropTypes
QueryEditor.propTypes = {
query: string.isRequired,
onUpdate: func.isRequired,
config: shape().isRequired,
- isInDataExplorer: bool,
- templates: arrayOf(
- shape({
- tempVar: string.isRequired,
- })
- ),
}
export default QueryEditor
diff --git a/ui/src/data_explorer/components/QueryMaker.js b/ui/src/data_explorer/components/QueryMaker.js
index ae73677ca1..66ceb3609c 100644
--- a/ui/src/data_explorer/components/QueryMaker.js
+++ b/ui/src/data_explorer/components/QueryMaker.js
@@ -1,181 +1,87 @@
import React, {PropTypes} from 'react'
-import QueryBuilder from './QueryBuilder'
-import QueryMakerTab from './QueryMakerTab'
+import QueryEditor from './QueryEditor'
+import EmptyQuery from 'src/shared/components/EmptyQuery'
+import QueryTabList from 'src/shared/components/QueryTabList'
+import SchemaExplorer from 'src/shared/components/SchemaExplorer'
import buildInfluxQLQuery from 'utils/influxql'
-import classnames from 'classnames'
-const {arrayOf, bool, func, node, number, shape, string} = PropTypes
+const rawTextBinder = (links, id, action) => text =>
+ action(links.queries, id, text)
-const QueryMaker = React.createClass({
- propTypes: {
- source: shape({
- links: shape({
- queries: string.isRequired,
- }).isRequired,
- }).isRequired,
- queries: arrayOf(shape({})).isRequired,
- timeRange: shape({
- upper: string,
- lower: string,
- }).isRequired,
- templates: arrayOf(
- shape({
- tempVar: string.isRequired,
- })
- ),
- isInDataExplorer: bool,
- actions: shape({
- chooseNamespace: func.isRequired,
- chooseMeasurement: func.isRequired,
- chooseTag: func.isRequired,
- groupByTag: func.isRequired,
- addQuery: func.isRequired,
- toggleField: func.isRequired,
- groupByTime: func.isRequired,
- toggleTagAcceptance: func.isRequired,
- applyFuncsToField: func.isRequired,
- editRawTextAsync: func.isRequired,
- }).isRequired,
- height: string,
- top: string,
- setActiveQueryIndex: func.isRequired,
- onDeleteQuery: func.isRequired,
- activeQueryIndex: number,
- children: node,
- layout: string,
- },
+const buildText = (q, timeRange) =>
+ q.rawText || buildInfluxQLQuery(q.range || timeRange, q) || ''
- handleAddQuery() {
- const newIndex = this.props.queries.length
- this.props.actions.addQuery()
- this.props.setActiveQueryIndex(newIndex)
- },
-
- handleAddRawQuery() {
- const newIndex = this.props.queries.length
- this.props.actions.addQuery({rawText: ''})
- this.props.setActiveQueryIndex(newIndex)
- },
-
- getActiveQuery() {
- const {queries, activeQueryIndex} = this.props
- const activeQuery = queries[activeQueryIndex]
- const defaultQuery = queries[0]
-
- return activeQuery || defaultQuery
- },
-
- render() {
- const {height, top, layout} = this.props
- return (
-
- {this.renderQueryTabList()}
- {this.renderQueryBuilder()}
-
- )
- },
-
- renderQueryBuilder() {
- const {
- timeRange,
- actions,
- source,
- templates,
- layout,
- isInDataExplorer,
- } = this.props
- const query = this.getActiveQuery()
-
- if (!query) {
- return (
-
-
This Graph has no Queries
-
-
- Add a Query
-
+const QueryMaker = ({
+ source,
+ actions,
+ queries,
+ timeRange,
+ onAddQuery,
+ activeQuery,
+ onDeleteQuery,
+ activeQueryIndex,
+ setActiveQueryIndex,
+}) =>
+
+
+ {activeQuery && activeQuery.id
+ ?
+
+
- )
- }
+ :
}
+
- // NOTE
- // the layout prop is intended to toggle between a horizontal and vertical layout
- // the layout will be horizontal by default
- // vertical layout is known as "panel" layout as it will be used to build
- // a "cell editor panel" though that term might change
- // Currently, if set to "panel" the only noticeable difference is that the
- // DatabaseList becomes DatabaseDropdown (more space efficient in vertical layout)
- // and is outside the container with measurements/tags/fields
- //
- // TODO:
- // - perhaps switch to something like "isVertical" and accept boolean instead of string
- // - more css/markup work to make the alternate appearance look good
+const {arrayOf, func, number, shape, string} = PropTypes
- return (
-
- )
- },
-
- renderQueryTabList() {
- const {
- queries,
- activeQueryIndex,
- onDeleteQuery,
- timeRange,
- setActiveQueryIndex,
- } = this.props
-
- return (
-
- {queries.map((q, i) => {
- return (
-
- )
- })}
- {this.props.children}
-
-
-
-
- )
- },
-})
-
-QueryMaker.defaultProps = {
- layout: 'default',
+QueryMaker.propTypes = {
+ source: shape({
+ links: shape({
+ queries: string.isRequired,
+ }).isRequired,
+ }).isRequired,
+ queries: arrayOf(shape({})).isRequired,
+ timeRange: shape({
+ upper: string,
+ lower: string,
+ }).isRequired,
+ actions: shape({
+ chooseNamespace: func.isRequired,
+ chooseMeasurement: func.isRequired,
+ chooseTag: func.isRequired,
+ groupByTag: func.isRequired,
+ addQuery: func.isRequired,
+ toggleField: func.isRequired,
+ groupByTime: func.isRequired,
+ toggleTagAcceptance: func.isRequired,
+ applyFuncsToField: func.isRequired,
+ editRawTextAsync: func.isRequired,
+ }).isRequired,
+ setActiveQueryIndex: func.isRequired,
+ onDeleteQuery: func.isRequired,
+ onAddQuery: func.isRequired,
+ activeQuery: shape({}),
+ activeQueryIndex: number,
}
+
export default QueryMaker
diff --git a/ui/src/data_explorer/components/QueryMakerTab.js b/ui/src/data_explorer/components/QueryMakerTab.js
index 03207c4b46..68f0c51a13 100644
--- a/ui/src/data_explorer/components/QueryMakerTab.js
+++ b/ui/src/data_explorer/components/QueryMakerTab.js
@@ -33,7 +33,11 @@ const QueryMakerTab = React.createClass({
{this.props.queryTabText}
-
+
)
},
diff --git a/ui/src/data_explorer/components/TagListItem.js b/ui/src/data_explorer/components/TagListItem.js
index 5575e6f980..73d2ee6642 100644
--- a/ui/src/data_explorer/components/TagListItem.js
+++ b/ui/src/data_explorer/components/TagListItem.js
@@ -76,7 +76,12 @@ const TagListItem = React.createClass({
active: selectedTagValues.indexOf(v) > -1,
})
return (
-
+
{v}
@@ -103,6 +108,7 @@ const TagListItem = React.createClass({
diff --git a/ui/src/data_explorer/components/VisHeader.js b/ui/src/data_explorer/components/VisHeader.js
index 8a822a52a5..4f118145ad 100644
--- a/ui/src/data_explorer/components/VisHeader.js
+++ b/ui/src/data_explorer/components/VisHeader.js
@@ -11,6 +11,7 @@ const VisHeader = ({views, view, onToggleView, name}) =>
key={v}
onClick={() => onToggleView(v)}
className={classnames({active: view === v})}
+ data-test={`data-${v}`}
>
{_.upperFirst(v)}
diff --git a/ui/src/data_explorer/components/WriteDataBody.js b/ui/src/data_explorer/components/WriteDataBody.js
index 2cb59caab0..d94521a890 100644
--- a/ui/src/data_explorer/components/WriteDataBody.js
+++ b/ui/src/data_explorer/components/WriteDataBody.js
@@ -24,6 +24,7 @@ const WriteDataBody = ({
onKeyUp={handleKeyUp}
onChange={handleEdit}
autoFocus={true}
+ data-test="manual-entry-field"
/>
:
Write
diff --git a/ui/src/data_explorer/components/WriteDataHeader.js b/ui/src/data_explorer/components/WriteDataHeader.js
index beba8b7420..80c704f205 100644
--- a/ui/src/data_explorer/components/WriteDataHeader.js
+++ b/ui/src/data_explorer/components/WriteDataHeader.js
@@ -27,6 +27,7 @@ const WriteDataHeader = ({
toggleWriteView(true)}
className={isManual ? 'active' : ''}
+ data-test="manual-entry-button"
>
Manual Entry
diff --git a/ui/src/data_explorer/containers/DataExplorer.js b/ui/src/data_explorer/containers/DataExplorer.js
index 5ec4ee93f8..ae2fb2b34d 100644
--- a/ui/src/data_explorer/containers/DataExplorer.js
+++ b/ui/src/data_explorer/containers/DataExplorer.js
@@ -1,4 +1,4 @@
-import React, {PropTypes} from 'react'
+import React, {PropTypes, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
@@ -18,64 +18,44 @@ import {setAutoRefresh} from 'shared/actions/app'
import * as dataExplorerActionCreators from 'src/data_explorer/actions/view'
import {writeLineProtocolAsync} from 'src/data_explorer/actions/view/write'
-const {arrayOf, func, number, shape, string} = PropTypes
+class DataExplorer extends Component {
+ constructor(props) {
+ super(props)
-const DataExplorer = React.createClass({
- propTypes: {
- source: shape({
- links: shape({
- proxy: string.isRequired,
- self: string.isRequired,
- queries: string.isRequired,
- }).isRequired,
- }).isRequired,
- queryConfigs: arrayOf(shape({})).isRequired,
- queryConfigActions: shape({
- editQueryStatus: func.isRequired,
- }).isRequired,
- autoRefresh: number.isRequired,
- handleChooseAutoRefresh: func.isRequired,
- timeRange: shape({
- upper: string,
- lower: string,
- }).isRequired,
- setTimeRange: func.isRequired,
- dataExplorer: shape({
- queryIDs: arrayOf(string).isRequired,
- }).isRequired,
- writeLineProtocol: func.isRequired,
- errorThrownAction: func.isRequired,
- },
-
- childContextTypes: {
- source: shape({
- links: shape({
- proxy: string.isRequired,
- self: string.isRequired,
- }).isRequired,
- }).isRequired,
- },
-
- getChildContext() {
- return {source: this.props.source}
- },
-
- getInitialState() {
- return {
+ this.state = {
activeQueryIndex: 0,
showWriteForm: false,
}
- },
+ }
+
+ getChildContext() {
+ return {source: this.props.source}
+ }
handleSetActiveQueryIndex(index) {
this.setState({activeQueryIndex: index})
- },
+ }
handleDeleteQuery(index) {
- const {queryConfigs} = this.props
+ const {queryConfigs, queryConfigActions} = this.props
const query = queryConfigs[index]
- this.props.queryConfigActions.deleteQuery(query.id)
- },
+ queryConfigActions.deleteQuery(query.id)
+ }
+
+ handleAddQuery() {
+ const newIndex = this.props.queryConfigs.length
+ this.props.queryConfigActions.addQuery()
+ this.handleSetActiveQueryIndex(newIndex)
+ }
+
+ getActiveQuery() {
+ const {activeQueryIndex} = this.state
+ const {queryConfigs} = this.props
+ const activeQuery = queryConfigs[activeQueryIndex]
+ const defaultQuery = queryConfigs[0]
+
+ return activeQuery || defaultQuery
+ }
render() {
const {
@@ -89,6 +69,7 @@ const DataExplorer = React.createClass({
source,
writeLineProtocol,
} = this.props
+
const {activeQueryIndex, showWriteForm} = this.state
const selectedDatabase = _.get(
queryConfigs,
@@ -128,10 +109,11 @@ const DataExplorer = React.createClass({
actions={queryConfigActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
- isInDataExplorer={true}
- setActiveQueryIndex={this.handleSetActiveQueryIndex}
- onDeleteQuery={this.handleDeleteQuery}
+ setActiveQueryIndex={::this.handleSetActiveQueryIndex}
+ onDeleteQuery={::this.handleDeleteQuery}
+ onAddQuery={::this.handleAddQuery}
activeQueryIndex={activeQueryIndex}
+ activeQuery={::this.getActiveQuery()}
/>
)
- },
-})
+ }
+}
-function mapStateToProps(state) {
+const {arrayOf, func, number, shape, string} = PropTypes
+
+DataExplorer.propTypes = {
+ source: shape({
+ links: shape({
+ proxy: string.isRequired,
+ self: string.isRequired,
+ queries: string.isRequired,
+ }).isRequired,
+ }).isRequired,
+ queryConfigs: arrayOf(shape({})).isRequired,
+ queryConfigActions: shape({
+ editQueryStatus: func.isRequired,
+ }).isRequired,
+ autoRefresh: number.isRequired,
+ handleChooseAutoRefresh: func.isRequired,
+ timeRange: shape({
+ upper: string,
+ lower: string,
+ }).isRequired,
+ setTimeRange: func.isRequired,
+ dataExplorer: shape({
+ queryIDs: arrayOf(string).isRequired,
+ }).isRequired,
+ writeLineProtocol: func.isRequired,
+ errorThrownAction: func.isRequired,
+}
+
+DataExplorer.childContextTypes = {
+ source: shape({
+ links: shape({
+ proxy: string.isRequired,
+ self: string.isRequired,
+ }).isRequired,
+ }).isRequired,
+}
+
+const mapStateToProps = state => {
const {
app: {persisted: {autoRefresh}},
dataExplorer,
@@ -165,7 +184,7 @@ function mapStateToProps(state) {
}
}
-function mapDispatchToProps(dispatch) {
+const mapDispatchToProps = dispatch => {
return {
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
errorThrownAction: bindActionCreators(errorThrown, dispatch),
diff --git a/ui/src/data_explorer/containers/Header.js b/ui/src/data_explorer/containers/Header.js
index 6913d778ae..32cd0e0bff 100644
--- a/ui/src/data_explorer/containers/Header.js
+++ b/ui/src/data_explorer/containers/Header.js
@@ -49,7 +49,11 @@ const Header = React.createClass({
-
+
Write Data
diff --git a/ui/src/hosts/apis/index.js b/ui/src/hosts/apis/index.js
index 225ac59a3e..1c0324dcfb 100644
--- a/ui/src/hosts/apis/index.js
+++ b/ui/src/hosts/apis/index.js
@@ -10,7 +10,7 @@ export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM "system" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
SELECT mean("Percent_Processor_Time") FROM win_cpu WHERE time > now() - 10m GROUP BY host;
SELECT mean("Processor_Queue_Length") FROM win_system WHERE time > now() - 10s GROUP BY host;
- SELECT non_negative_derivative(mean("System_Up_Time")) AS deltaUptime FROM "telegraf"."autogen"."win_uptime" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
+ SELECT non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM "telegraf"."autogen"."win_system" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
SHOW TAG VALUES FROM /win_system|system/ WITH KEY = "host"`,
db: telegrafDB,
}).then(resp => {
@@ -72,9 +72,11 @@ export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
})
winUptimeSeries.forEach(s => {
- const uptimeIndex = s.columns.findIndex(col => col === 'deltaUptime')
- hosts[s.tags.host].deltaUptime =
- s.values[s.values.length - 1][uptimeIndex]
+ const winUptimeIndex = s.columns.findIndex(
+ col => col === 'winDeltaUptime'
+ )
+ hosts[s.tags.host].winDeltaUptime =
+ s.values[s.values.length - 1][winUptimeIndex]
})
return hosts
diff --git a/ui/src/hosts/components/HostsTable.js b/ui/src/hosts/components/HostsTable.js
index 365eb6e721..bd04942725 100644
--- a/ui/src/hosts/components/HostsTable.js
+++ b/ui/src/hosts/components/HostsTable.js
@@ -209,7 +209,9 @@ const HostRow = React.createClass({
0 ? 'dot-success' : 'dot-critical'
+ Math.max(host.deltaUptime || 0, host.winDeltaUptime || 0) > 0
+ ? 'dot-success'
+ : 'dot-critical'
)}
/>
diff --git a/ui/src/index.js b/ui/src/index.js
index 7b656caffc..0cc35d03a6 100644
--- a/ui/src/index.js
+++ b/ui/src/index.js
@@ -1,3 +1,5 @@
+import 'babel-polyfill'
+
import React from 'react'
import {render} from 'react-dom'
import {Provider} from 'react-redux'
@@ -47,7 +49,8 @@ const errorsQueue = []
const rootNode = document.getElementById('react-root')
-const basepath = rootNode.dataset.basepath || ''
+// Older method used for pre-IE 11 compatibility
+const basepath = rootNode.getAttribute('data-basepath') || ''
window.basepath = basepath
const browserHistory = useRouterHistory(createHistory)({
basename: basepath, // this is written in when available by the URL prefixer middleware
diff --git a/ui/src/kapacitor/components/DataSection.js b/ui/src/kapacitor/components/DataSection.js
index 00946d5f44..88e73a3b34 100644
--- a/ui/src/kapacitor/components/DataSection.js
+++ b/ui/src/kapacitor/components/DataSection.js
@@ -2,9 +2,9 @@ import React, {PropTypes} from 'react'
import buildInfluxQLQuery from 'utils/influxql'
import classnames from 'classnames'
-import DatabaseList from '../../data_explorer/components/DatabaseList'
-import MeasurementList from '../../data_explorer/components/MeasurementList'
-import FieldList from '../../data_explorer/components/FieldList'
+import DatabaseList from 'src/shared/components/DatabaseList'
+import MeasurementList from 'src/shared/components/MeasurementList'
+import FieldList from 'src/shared/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
diff --git a/ui/src/kapacitor/components/RuleMessageOptions.js b/ui/src/kapacitor/components/RuleMessageOptions.js
index 77fd068636..0dc1690d3d 100644
--- a/ui/src/kapacitor/components/RuleMessageOptions.js
+++ b/ui/src/kapacitor/components/RuleMessageOptions.js
@@ -44,7 +44,7 @@ class RuleMessageOptions extends Component {
@@ -83,7 +83,7 @@ class RuleMessageOptions extends Component {
className="form-control input-sm form-malachite"
style={{
margin: '0 15px 0 5px',
- flex: '1 0 0',
+ flex: '1 0 0%',
}}
type="text"
placeholder={placeholder}
diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js
index 2574c19512..1997e1b6c9 100644
--- a/ui/src/shared/components/AutoRefresh.js
+++ b/ui/src/shared/components/AutoRefresh.js
@@ -215,7 +215,7 @@ const AutoRefresh = ComposedComponent => {
return (
-
No Results
+
No Results
)
},
diff --git a/ui/src/data_explorer/components/DatabaseList.js b/ui/src/shared/components/DatabaseList.js
similarity index 97%
rename from ui/src/data_explorer/components/DatabaseList.js
rename to ui/src/shared/components/DatabaseList.js
index 23ad6143a1..db1a34ab7c 100644
--- a/ui/src/data_explorer/components/DatabaseList.js
+++ b/ui/src/shared/components/DatabaseList.js
@@ -83,6 +83,7 @@ const DatabaseList = React.createClass({
})}
key={`${database}..${retentionPolicy}`}
onClick={_.wrap(namespace, onChooseNamespace)}
+ data-test={`query-builder-list-item-database-${database}`}
>
{database}.{retentionPolicy}
diff --git a/ui/src/shared/components/EmptyQuery.js b/ui/src/shared/components/EmptyQuery.js
new file mode 100644
index 0000000000..e37b9cf72b
--- /dev/null
+++ b/ui/src/shared/components/EmptyQuery.js
@@ -0,0 +1,18 @@
+import React, {PropTypes} from 'react'
+
+const EmptyQueryState = ({onAddQuery}) =>
+
+
This Graph has no Queries
+
+
+ Add a Query
+
+
+
+const {func} = PropTypes
+
+EmptyQueryState.propTypes = {
+ onAddQuery: func.isRequired,
+}
+
+export default EmptyQueryState
diff --git a/ui/src/data_explorer/components/FieldList.js b/ui/src/shared/components/FieldList.js
similarity index 100%
rename from ui/src/data_explorer/components/FieldList.js
rename to ui/src/shared/components/FieldList.js
diff --git a/ui/src/shared/components/FunctionSelector.js b/ui/src/shared/components/FunctionSelector.js
index 75be5eb5ae..5abc26dd7d 100644
--- a/ui/src/shared/components/FunctionSelector.js
+++ b/ui/src/shared/components/FunctionSelector.js
@@ -74,6 +74,7 @@ class FunctionSelector extends Component {
Apply
@@ -90,6 +91,7 @@ class FunctionSelector extends Component {
f,
singleSelect ? this.onSingleSelect : this.onSelect
)}
+ data-test={`function-selector-item-${f}`}
>
{f}
diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js
index 61f1fc11ad..f43e6c7fba 100644
--- a/ui/src/shared/components/LayoutRenderer.js
+++ b/ui/src/shared/components/LayoutRenderer.js
@@ -127,7 +127,7 @@ class LayoutRenderer extends Component {
}
return (
-
No Results
+
No Results
)
}
diff --git a/ui/src/data_explorer/components/MeasurementList.js b/ui/src/shared/components/MeasurementList.js
similarity index 98%
rename from ui/src/data_explorer/components/MeasurementList.js
rename to ui/src/shared/components/MeasurementList.js
index d69d751989..5b9b993a3a 100644
--- a/ui/src/data_explorer/components/MeasurementList.js
+++ b/ui/src/shared/components/MeasurementList.js
@@ -144,6 +144,7 @@ const MeasurementList = React.createClass({
className={classnames('query-builder--list-item', {
active: isActive,
})}
+ data-test={`query-builder-list-item-measurement-${measurement}`}
>
diff --git a/ui/src/shared/components/QueryStatus.js b/ui/src/shared/components/QueryStatus.js
new file mode 100644
index 0000000000..951fa65ba8
--- /dev/null
+++ b/ui/src/shared/components/QueryStatus.js
@@ -0,0 +1,53 @@
+import React, {PropTypes} from 'react'
+import LoadingDots from 'shared/components/LoadingDots'
+import classnames from 'classnames'
+
+const QueryStatus = ({status, children}) => {
+ if (!status) {
+ return
+ }
+
+ if (status.loading) {
+ return (
+
+
+ {children}
+
+ )
+ }
+
+ return (
+
+
+
+ {status.error || status.warn || status.success}
+
+ {children}
+
+ )
+}
+
+const {node, shape, string} = PropTypes
+
+QueryStatus.propTypes = {
+ status: shape({
+ error: string,
+ success: string,
+ warn: string,
+ }),
+ children: node,
+}
+
+export default QueryStatus
diff --git a/ui/src/shared/components/QueryTabList.js b/ui/src/shared/components/QueryTabList.js
new file mode 100644
index 0000000000..49d7225764
--- /dev/null
+++ b/ui/src/shared/components/QueryTabList.js
@@ -0,0 +1,49 @@
+import React, {PropTypes} from 'react'
+import QueryMakerTab from 'src/data_explorer/components/QueryMakerTab'
+import buildInfluxQLQuery from 'utils/influxql'
+
+const QueryTabList = ({
+ queries,
+ timeRange,
+ onAddQuery,
+ onDeleteQuery,
+ activeQueryIndex,
+ setActiveQueryIndex,
+}) =>
+
+ {queries.map((q, i) =>
+
+ )}
+
+
+
+
+
+const {arrayOf, func, number, shape, string} = PropTypes
+
+QueryTabList.propTypes = {
+ queries: arrayOf(shape({})).isRequired,
+ timeRange: shape({
+ upper: string,
+ lower: string,
+ }).isRequired,
+ onAddQuery: func.isRequired,
+ onDeleteQuery: func.isRequired,
+ activeQueryIndex: number.isRequired,
+ setActiveQueryIndex: func.isRequired,
+}
+
+export default QueryTabList
diff --git a/ui/src/shared/components/SchemaExplorer.js b/ui/src/shared/components/SchemaExplorer.js
new file mode 100644
index 0000000000..24089aa23b
--- /dev/null
+++ b/ui/src/shared/components/SchemaExplorer.js
@@ -0,0 +1,62 @@
+import React, {PropTypes} from 'react'
+
+import DatabaseList from 'src/shared/components/DatabaseList'
+import MeasurementList from 'src/shared/components/MeasurementList'
+import FieldList from 'src/shared/components/FieldList'
+
+const actionBinder = (id, action) => item => action(id, item)
+
+const SchemaExplorer = ({
+ query,
+ query: {id},
+ actions: {
+ chooseTag,
+ groupByTag,
+ groupByTime,
+ chooseNamespace,
+ chooseMeasurement,
+ applyFuncsToField,
+ toggleTagAcceptance,
+ toggleFieldWithGroupByInterval,
+ },
+}) =>
+
+
+
+
+
+
+const {func, shape, string} = PropTypes
+
+SchemaExplorer.propTypes = {
+ query: shape({
+ id: string,
+ }).isRequired,
+ actions: shape({
+ chooseNamespace: func.isRequired,
+ chooseMeasurement: func.isRequired,
+ applyFuncsToField: func.isRequired,
+ chooseTag: func.isRequired,
+ groupByTag: func.isRequired,
+ toggleField: func.isRequired,
+ groupByTime: func.isRequired,
+ toggleTagAcceptance: func.isRequired,
+ editRawTextAsync: func.isRequired,
+ }).isRequired,
+}
+
+export default SchemaExplorer
diff --git a/ui/src/shared/middleware/resizeLayout.js b/ui/src/shared/middleware/resizeLayout.js
index df6d3c2bf3..d7a657d4ba 100644
--- a/ui/src/shared/middleware/resizeLayout.js
+++ b/ui/src/shared/middleware/resizeLayout.js
@@ -7,7 +7,10 @@ export default function resizeLayout() {
action.type === 'ENABLE_PRESENTATION_MODE' ||
action.type === 'DISABLE_PRESENTATION_MODE'
) {
- window.dispatchEvent(new Event('resize'))
+ // Uses longer event object creation method due to IE compatibility.
+ const evt = document.createEvent('HTMLEvents')
+ evt.initEvent('resize', false, true)
+ window.dispatchEvent(evt)
}
}
}
diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss
index 654c309042..f38b0420a6 100644
--- a/ui/src/style/components/dygraphs.scss
+++ b/ui/src/style/components/dygraphs.scss
@@ -161,10 +161,10 @@
line-height: 30px;
font-weight: 600;
color: $g13-mist;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.dygraph-legend--filter {
- flex: 1 0 0;
+ flex: 1 0 0%;
margin-top: 8px;
}
.dygraph-legend--divider {
diff --git a/ui/src/style/components/query-builder.scss b/ui/src/style/components/query-builder.scss
index b9642a98e7..40ec78de1d 100644
--- a/ui/src/style/components/query-builder.scss
+++ b/ui/src/style/components/query-builder.scss
@@ -5,7 +5,7 @@
*/
.query-builder {
width: 100%;
- flex: 1 0 0;
+ flex: 1 0 0%;
display: flex;
align-items: stretch;
flex-wrap: nowrap;
@@ -13,10 +13,10 @@
.query-builder--column {
display: flex;
flex-direction: column;
- flex: 2 0 0;
+ flex: 2 0 0%;
}
.query-builder--column-db {
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.query-builder--heading {
@include no-user-select();
@@ -39,7 +39,7 @@
}
.query-builder--list,
.query-builder--list-empty {
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.query-builder--list {
padding: 0;
@@ -99,7 +99,7 @@
}
/* Filter Element */
.query-builder--filter {
- flex: 1 0 0;
+ flex: 1 0 0%;
display: flex;
& > span {
diff --git a/ui/src/style/components/query-editor.scss b/ui/src/style/components/query-editor.scss
index 0874272cca..f5045dc005 100644
--- a/ui/src/style/components/query-editor.scss
+++ b/ui/src/style/components/query-editor.scss
@@ -64,7 +64,7 @@
}
}
.query-status-output {
- flex: 1 0 0;
+ flex: 1 0 0%;
display: inline-block;
color: $query-editor--status-default;
white-space: nowrap;
diff --git a/ui/src/style/components/query-maker.scss b/ui/src/style/components/query-maker.scss
index 172f1832b2..d74c40dbdf 100644
--- a/ui/src/style/components/query-maker.scss
+++ b/ui/src/style/components/query-maker.scss
@@ -94,7 +94,7 @@ $query-editor-tab-active: $g3-castle;
height: $query-maker--tabs-height;
margin: 0 2px 0 0;
max-width: $query-maker--tab-width;
- flex: 1 0 0;
+ flex: 1 0 0%;
display: flex;
align-items: center;
justify-content: space-between;
@@ -170,7 +170,7 @@ $query-editor-tab-active: $g3-castle;
}
.query-maker--tab-contents,
.query-maker--empty {
- flex: 1 0 0;
+ flex: 1 0 0%;
margin: 0 0 $query-maker--gutter 0;
background-color: $query-maker--tab-contents-bg;
}
diff --git a/ui/src/style/components/tables.scss b/ui/src/style/components/tables.scss
index 632f17e193..9425cce4f8 100644
--- a/ui/src/style/components/tables.scss
+++ b/ui/src/style/components/tables.scss
@@ -191,7 +191,7 @@ $table-tab-scrollbar-height: 6px;
flex-direction: column;
align-items: stretch;
- > .panel-body {flex: 1 0 0;}
+ > .panel-body {flex: 1 0 0%;}
.generic-empty-state {height: 100%;}
}
}
@@ -234,7 +234,7 @@ $table-tab-scrollbar-height: 6px;
color: $g17-whisper;
}
.alert-history-table--tbody {
- flex: 1 0 0;
+ flex: 1 0 0%;
width: 100%;
}
.alert-history-table--tr {
diff --git a/ui/src/style/components/template-control-bar.scss b/ui/src/style/components/template-control-bar.scss
index 6ad2375e24..074e42508a 100644
--- a/ui/src/style/components/template-control-bar.scss
+++ b/ui/src/style/components/template-control-bar.scss
@@ -42,7 +42,7 @@ button.btn.template-control--manage {
}
.template-control--controls {
display: flex;
- flex: 1 0 0;
+ flex: 1 0 0%;
flex-wrap: wrap;
}
.template-control--empty {
@@ -64,7 +64,7 @@ button.btn.template-control--manage {
.dropdown {
order: 2;
margin: 0;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.dropdown-toggle {
border-radius: 0 0 $radius-small $radius-small;
diff --git a/ui/src/style/components/template-variables-manager.scss b/ui/src/style/components/template-variables-manager.scss
index 622e27c584..ad85d3c211 100644
--- a/ui/src/style/components/template-variables-manager.scss
+++ b/ui/src/style/components/template-variables-manager.scss
@@ -166,7 +166,7 @@ $tvmp-table-gutter: 8px;
> *:last-child {margin-right: 0;}
.dropdown {
- flex: 1 0 0;
+ flex: 1 0 0%;
& > .dropdown-toggle {width: 100%;}
}
diff --git a/ui/src/style/pages/admin.scss b/ui/src/style/pages/admin.scss
index 8c1cce54a1..fc66cc6f34 100644
--- a/ui/src/style/pages/admin.scss
+++ b/ui/src/style/pages/admin.scss
@@ -103,7 +103,7 @@ table.table-highlight > tbody > tr.admin-table--edit-row:hover {
.form-control {
margin: 0 4px 0 0;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
}
pre.admin-table--query {
@@ -118,7 +118,7 @@ pre.admin-table--query {
align-items: center;
> .form-control {
- flex: 1 0 0;
+ flex: 1 0 0%;
margin-right: 4px;
}
}
@@ -157,7 +157,7 @@ pre.admin-table--query {
.form-control {
margin: 0 8px 0 0;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
}
diff --git a/ui/src/style/pages/config-endpoints.scss b/ui/src/style/pages/config-endpoints.scss
index 37aac2ca1f..44562000d3 100644
--- a/ui/src/style/pages/config-endpoints.scss
+++ b/ui/src/style/pages/config-endpoints.scss
@@ -18,7 +18,7 @@ $config-endpoint-tab-bg-active: $g3-castle;
align-items: stretch;
}
.config-endpoint--tabs {
- flex: 0 0 0;
+ flex: 0 0 0%;
display: flex;
.btn-group.tab-group {
@@ -27,7 +27,7 @@ $config-endpoint-tab-bg-active: $g3-castle;
border-radius: $radius 0 0 $radius;
margin: 0;
display: flex;
- flex: 1 0 0;
+ flex: 1 0 0%;
flex-direction: column;
align-items: stretch;
@@ -58,8 +58,8 @@ $config-endpoint-tab-bg-active: $g3-castle;
}
}
.config-endpoint--tab-contents {
- flex: 1 0 0;
+ flex: 1 0 0%;
background-color: $config-endpoint-tab-bg-active;
border-radius: 0 $radius $radius 0;
padding: 16px 42px;
-}
\ No newline at end of file
+}
diff --git a/ui/src/style/pages/overlay-technology.scss b/ui/src/style/pages/overlay-technology.scss
index bb02ff605b..74c7fa6e6a 100644
--- a/ui/src/style/pages/overlay-technology.scss
+++ b/ui/src/style/pages/overlay-technology.scss
@@ -86,7 +86,7 @@ $overlay-z: 100;
margin: 0 15%;
}
.overlay-technology .query-maker {
- flex: 1 0 0;
+ flex: 1 0 0%;
padding: 0 18px;
margin: 0;
background-color: $g2-kevlar;
diff --git a/ui/src/style/theme/bootstrap-theme.scss b/ui/src/style/theme/bootstrap-theme.scss
index 8345c8ee16..dc9e8897fd 100755
--- a/ui/src/style/theme/bootstrap-theme.scss
+++ b/ui/src/style/theme/bootstrap-theme.scss
@@ -3887,7 +3887,7 @@ p .label {
text-overflow: ellipsis;
white-space: nowrap;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.dropdown-toggle.btn-xs .caret {
right: 7px;
@@ -4041,7 +4041,7 @@ p .label {
outline: none;
transition: color .25s ease;
- flex: 1 0 0;
+ flex: 1 0 0%;
}
.dropdown-menu li.dropdown-item > a,
.dropdown-menu li.dropdown-item > a:hover,
diff --git a/ui/src/utils/buildQueriesForGraphs.js b/ui/src/utils/buildQueriesForGraphs.js
new file mode 100644
index 0000000000..39ff302e5f
--- /dev/null
+++ b/ui/src/utils/buildQueriesForGraphs.js
@@ -0,0 +1,17 @@
+import buildInfluxQLQuery from 'utils/influxql'
+
+const buildQueries = (proxy, queryConfigs, timeRange) => {
+ const statements = queryConfigs.map(query => {
+ const text =
+ query.rawText || buildInfluxQLQuery(query.range || timeRange, query)
+ return {text, id: query.id, queryConfig: query}
+ })
+
+ const queries = statements.filter(s => s.text !== null).map(s => {
+ return {host: [proxy], text: s.text, id: s.id, queryConfig: s.queryConfig}
+ })
+
+ return queries
+}
+
+export default buildQueries
diff --git a/ui/tests/DataExplorer.js b/ui/tests/DataExplorer.js
new file mode 100644
index 0000000000..bcaeb34c71
--- /dev/null
+++ b/ui/tests/DataExplorer.js
@@ -0,0 +1,112 @@
+const src = process.argv.find(s => s.includes('--src=')).replace('--src=', '')
+const dataExplorerUrl = `http://localhost:8888/sources/${src}/chronograf/data-explorer`
+const dataTest = s => `[data-test="${s}"]`
+
+module.exports = {
+ 'Data Explorer (functional) - SHOW DATABASES'(browser) {
+ browser
+ // Navigate to the Data Explorer
+ .url(dataExplorerUrl)
+ // Open a new query tab
+ .waitForElementVisible(dataTest('add-query-button'), 1000)
+ .click(dataTest('add-query-button'))
+ .waitForElementVisible(dataTest('query-editor-field'), 1000)
+ // Drop any existing testing database
+ .setValue(dataTest('query-editor-field'), 'DROP DATABASE "testing"\n')
+ .click(dataTest('query-editor-field'))
+ .pause(500)
+ // Create a new testing database
+ .clearValue(dataTest('query-editor-field'))
+ .setValue(dataTest('query-editor-field'), 'CREATE DATABASE "testing"\n')
+ .click(dataTest('query-editor-field'))
+ .pause(2000)
+ .refresh()
+ .waitForElementVisible(dataTest('query-editor-field'), 1000)
+ .clearValue(dataTest('query-editor-field'))
+ .setValue(dataTest('query-editor-field'), 'SHOW DATABASES\n')
+ .click(dataTest('query-editor-field'))
+ .pause(1000)
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-database-testing'),
+ 5000
+ )
+ .assert.containsText(
+ dataTest('query-builder-list-item-database-testing'),
+ 'testing'
+ )
+ .end()
+ },
+ 'Query Builder'(browser) {
+ browser
+ // Navigate to the Data Explorer
+ .url(dataExplorerUrl)
+ // Check to see that there are no results displayed
+ .waitForElementVisible(dataTest('data-explorer-no-results'), 5000)
+ .assert.containsText(dataTest('data-explorer-no-results'), 'No Results')
+ // Open a new query tab
+ .waitForElementVisible(dataTest('new-query-button'), 1000)
+ .click(dataTest('new-query-button'))
+ // Select the testing database
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-database-testing'),
+ 1000
+ )
+ .click(dataTest('query-builder-list-item-database-testing'))
+ // Open up the Write Data dialog
+ .click(dataTest('write-data-button'))
+ // Set the dialog to manual entry mode
+ .waitForElementVisible(dataTest('manual-entry-button'), 1000)
+ .click(dataTest('manual-entry-button'))
+ // Enter some time-series data
+ .setValue(
+ dataTest('manual-entry-field'),
+ 'testing,test_measurement=1,test_measurement2=2 value=3,value2=4'
+ )
+ // Pause, then click the submit button
+ .pause(500)
+ .click(dataTest('write-data-submit-button'))
+ .pause(2000)
+ // Start building a query
+ // Select the testing measurement
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-measurement-testing'),
+ 2000
+ )
+ .click(dataTest('query-builder-list-item-measurement-testing'))
+ // Select both test measurements
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-tag-test_measurement'),
+ 1000
+ )
+ .click(dataTest('query-builder-list-item-tag-test_measurement'))
+ .click(dataTest('query-builder-list-item-tag-test_measurement2'))
+ .pause(500)
+ // Select both tag values
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-tag-value-1'),
+ 1000
+ )
+ .click(dataTest('query-builder-list-item-tag-value-1'))
+ .click(dataTest('query-builder-list-item-tag-value-2'))
+ .pause(500)
+ // Select both field values
+ .waitForElementVisible(
+ dataTest('query-builder-list-item-field-value'),
+ 1000
+ )
+ .click(dataTest('query-builder-list-item-field-value'))
+ .click(dataTest('query-builder-list-item-field-value2'))
+ .pause(500)
+ // Assert the built query string
+ .assert.containsText(
+ dataTest('query-editor-field'),
+ 'SELECT mean("value") AS "mean_value", mean("value2") AS "mean_value2" FROM "testing"."autogen"."testing" WHERE time > now() - 1h AND "test_measurement"=\'1\' AND "test_measurement2"=\'2\' GROUP BY time(10s)'
+ )
+ .click(dataTest('data-table'))
+ .click(dataTest('query-builder-list-item-function-value'))
+ .waitForElementVisible(dataTest('function-selector-item-mean'), 1000)
+ .click(dataTest('function-selector-item-mean'))
+ .click(dataTest('function-selector-apply'))
+ .end()
+ },
+}
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 8d89c10604..94370d9900 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -140,9 +140,9 @@ acorn@^3.0.0, acorn@^3.0.4, acorn@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-acorn@^4.0.1:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
+acorn@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
add-px-to-style@1.0.0:
version "1.0.0"
@@ -200,10 +200,18 @@ ansi-html@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.6.tgz#bda8e33dd2ee1c20f54c08eb405713cbfc0ed80e"
+ansi-regex@^0.2.0, ansi-regex@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9"
+
ansi-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107"
+ansi-styles@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de"
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1105,7 +1113,7 @@ babel-plugin-transform-strict-mode@^6.18.0:
babel-runtime "^6.0.0"
babel-types "^6.18.0"
-babel-polyfill@^6.13.0:
+babel-polyfill@^6.13.0, babel-polyfill@^6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.20.0.tgz#de4a371006139e20990aac0be367d398331204e7"
dependencies:
@@ -1452,7 +1460,7 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.6:
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.6, bluebird@^3.4.7:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
@@ -1642,6 +1650,16 @@ chai@^3.5.0:
deep-eql "^0.1.3"
type-detect "^1.0.0"
+chalk@0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174"
+ dependencies:
+ ansi-styles "^1.1.0"
+ escape-string-regexp "^1.0.0"
+ has-ansi "^0.1.0"
+ strip-ansi "^0.3.0"
+ supports-color "^0.2.0"
+
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -1679,7 +1697,7 @@ cheerio@^0.22.0:
lodash.reject "^4.4.0"
lodash.some "^4.4.0"
-chokidar@^1.0.0, chokidar@^1.4.1:
+chokidar@^1.0.0, chokidar@^1.4.1, chokidar@^1.4.3:
version "1.6.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
dependencies:
@@ -1832,6 +1850,10 @@ commander@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
+commander@2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
+
commander@2.8.x:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
@@ -1917,7 +1939,7 @@ concat-stream@1.5.0:
readable-stream "~2.0.0"
typedarray "~0.0.5"
-concat-stream@^1.4.6:
+concat-stream@^1.5.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
dependencies:
@@ -1925,6 +1947,19 @@ concat-stream@^1.4.6:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concurrently@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.0.tgz#8cf1b7707a6916a78a4ff5b77bb04dec54b379b2"
+ dependencies:
+ chalk "0.5.1"
+ commander "2.6.0"
+ date-fns "^1.23.0"
+ lodash "^4.5.1"
+ rx "2.3.24"
+ spawn-command "^0.0.2-1"
+ supports-color "^3.2.3"
+ tree-kill "^1.1.0"
+
configstore@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
@@ -2256,6 +2291,10 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+date-fns@^1.23.0:
+ version "1.28.5"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.5.tgz#257cfc45d322df45ef5658665967ee841cd73faf"
+
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -2276,11 +2315,11 @@ debug@2.3.3:
dependencies:
ms "0.7.2"
-debug@^2.1.1, debug@^2.2.0:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.5.2.tgz#50c295a53dbf1657146e0c1b21307275e90d49cb"
+debug@^2.1.1, debug@^2.2.0, debug@^2.6.3:
+ version "2.6.8"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
- ms "0.7.2"
+ ms "2.0.0"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
@@ -2706,7 +2745,7 @@ escape-string-regexp@1.0.2, escape-string-regexp@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1"
-escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -2753,23 +2792,40 @@ eslint-plugin-react@6.6.0:
doctrine "^1.2.2"
jsx-ast-utils "^1.3.3"
-eslint@3.9.1:
- version "3.9.1"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.9.1.tgz#5a8597706fc6048bc6061ac754d4a211d28f4f5b"
+eslint-watch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/eslint-watch/-/eslint-watch-3.1.2.tgz#b93b3eca08915f113dc900994f880db1364de4b3"
+ dependencies:
+ babel-polyfill "^6.20.0"
+ bluebird "^3.4.7"
+ chalk "^1.1.3"
+ chokidar "^1.4.3"
+ debug "^2.6.3"
+ keypress "^0.2.1"
+ lodash "^4.17.4"
+ optionator "^0.8.2"
+ source-map-support "^0.4.14"
+ text-table "^0.2.0"
+ unicons "0.0.3"
+
+eslint@^3.14.1:
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
dependencies:
babel-code-frame "^6.16.0"
chalk "^1.1.3"
- concat-stream "^1.4.6"
+ concat-stream "^1.5.2"
debug "^2.1.1"
- doctrine "^1.2.2"
+ doctrine "^2.0.0"
escope "^3.6.0"
- espree "^3.3.1"
+ espree "^3.4.0"
+ esquery "^1.0.0"
estraverse "^4.2.0"
esutils "^2.0.2"
file-entry-cache "^2.0.0"
glob "^7.0.3"
- globals "^9.2.0"
- ignore "^3.1.5"
+ globals "^9.14.0"
+ ignore "^3.2.0"
imurmurhash "^0.1.4"
inquirer "^0.12.0"
is-my-json-valid "^2.10.0"
@@ -2787,16 +2843,16 @@ eslint@3.9.1:
require-uncached "^1.0.2"
shelljs "^0.7.5"
strip-bom "^3.0.0"
- strip-json-comments "~1.0.1"
+ strip-json-comments "~2.0.1"
table "^3.7.8"
text-table "~0.2.0"
user-home "^2.0.0"
-espree@^3.3.1:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c"
+espree@^3.4.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.0.tgz#98358625bdd055861ea27e2867ea729faf463d8d"
dependencies:
- acorn "^4.0.1"
+ acorn "^5.1.1"
acorn-jsx "^3.0.0"
esprima-fb@^15001.1.0-dev-harmony-fb:
@@ -2815,6 +2871,12 @@ esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+esquery@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
+ dependencies:
+ estraverse "^4.0.0"
+
esrecurse@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
@@ -2826,7 +2888,7 @@ estraverse@^1.9.1:
version "1.9.3"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
-estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
@@ -3331,7 +3393,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@~7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-globals@^9.0.0, globals@^9.2.0:
+globals@^9.0.0, globals@^9.14.0:
version "9.14.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
@@ -3394,6 +3456,12 @@ har-validator@~2.0.6:
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
+has-ansi@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e"
+ dependencies:
+ ansi-regex "^0.2.0"
+
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -3596,9 +3664,9 @@ ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
-ignore@^3.1.5:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
+ignore@^3.2.0:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
immutable@^3.8.1:
version "3.8.1"
@@ -4147,6 +4215,10 @@ keycode@^2.1.1:
version "2.1.8"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.8.tgz#94d2b7098215eff0e8f9a8931d5a59076c4532fb"
+keypress@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.2.1.tgz#1e80454250018dbad4c3fe94497d6e67b6269c77"
+
kind-of@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
@@ -4428,7 +4500,7 @@ lodash.words@^3.0.0:
dependencies:
lodash._root "^3.0.0"
-lodash@4.x.x, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.0, lodash@^4.16.4, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0:
+lodash@4.x.x, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.0, lodash@^4.16.4, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.5.1:
version "4.17.3"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.3.tgz#557ed7d2a9438cac5fd5a43043ca60cb455e01f7"
@@ -4436,6 +4508,10 @@ lodash@^3.8.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+lodash@^4.17.4:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
lodash@~4.16.4:
version "4.16.6"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
@@ -4697,6 +4773,10 @@ ms@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
mustache@^2.2.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
@@ -6352,6 +6432,10 @@ rx-lite@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+rx@2.3.24:
+ version "2.3.24"
+ resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
+
samsam@1.1.2, samsam@~1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
@@ -6668,11 +6752,11 @@ source-map-resolve@^0.3.0:
source-map-url "~0.3.0"
urix "~0.1.0"
-source-map-support@^0.4.2:
- version "0.4.8"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.8.tgz#4871918d8a3af07289182e974e32844327b2e98b"
+source-map-support@^0.4.14, source-map-support@^0.4.2:
+ version "0.4.15"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
dependencies:
- source-map "^0.5.3"
+ source-map "^0.5.6"
source-map-url@~0.3.0:
version "0.3.0"
@@ -6704,6 +6788,10 @@ spawn-args@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/spawn-args/-/spawn-args-0.2.0.tgz#fb7d0bd1d70fd4316bd9e3dec389e65f9d6361bb"
+spawn-command@^0.0.2-1:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
+
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -6823,6 +6911,12 @@ stringstream@~0.0.4:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+strip-ansi@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220"
+ dependencies:
+ ansi-regex "^0.2.1"
+
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -6845,10 +6939,14 @@ strip-indent@^1.0.1:
dependencies:
get-stdin "^4.0.1"
-strip-json-comments@~1.0.1, strip-json-comments@~1.0.4:
+strip-json-comments@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
style-loader@0.13.1, style-loader@^0.13.0:
version "0.13.1"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.1.tgz#468280efbc0473023cd3a6cd56e33b5a1d7fc3a9"
@@ -6877,9 +6975,9 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
+supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
has-flag "^1.0.0"
@@ -6983,7 +7081,7 @@ testem@^1.2.1:
tap-parser "^3.0.2"
xmldom "^0.1.19"
-text-table@~0.2.0:
+text-table@^0.2.0, text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -7057,6 +7155,10 @@ tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+tree-kill@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.1.0.tgz#c963dcf03722892ec59cba569e940b71954d1729"
+
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -7131,6 +7233,10 @@ underscore@>=1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+unicons@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/unicons/-/unicons-0.0.3.tgz#6e6a7a1a6eaebb01ca3d8b12ad9687279eaba524"
+
uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"