Merge branch 'master' into chore/prop-on-div-error
commit
21548485a9
|
@ -17,3 +17,4 @@ npm-debug.log
|
||||||
.jssrc
|
.jssrc
|
||||||
.dev-jssrc
|
.dev-jssrc
|
||||||
.bindata
|
.bindata
|
||||||
|
ui/reports
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
## v1.3.7.0 [unreleased]
|
## v1.3.7.0 [unreleased]
|
||||||
### Bug Fixes
|
### 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. [#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. [#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
|
### Features
|
||||||
|
1. [#1863](https://github.com/influxdata/chronograf/pull/1863): Improve 'new-sources' server flag example by adding 'type' key
|
||||||
|
|
||||||
### UI Improvements
|
### UI Improvements
|
||||||
1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay
|
1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -193,32 +194,103 @@ type GroupByVar struct {
|
||||||
|
|
||||||
// Exec is responsible for extracting the Duration from the query
|
// Exec is responsible for extracting the Duration from the query
|
||||||
func (g *GroupByVar) Exec(query string) {
|
func (g *GroupByVar) Exec(query string) {
|
||||||
whereClause := "WHERE time > now() - "
|
whereClause := "WHERE"
|
||||||
start := strings.Index(query, whereClause)
|
start := strings.Index(query, whereClause)
|
||||||
if start == -1 {
|
if start == -1 {
|
||||||
// no where clause
|
// no where clause
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// reposition start to the END of the where clause
|
// reposition start to after the 'where' keyword
|
||||||
durStr := query[start+len(whereClause):]
|
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
|
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) {
|
if unicode.IsSpace(rn) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
pos++
|
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 {
|
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 {
|
func (g *GroupByVar) String() string {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
|
||||||
newCell.H = cell.H
|
newCell.H = cell.H
|
||||||
newCell.Name = cell.Name
|
newCell.Name = cell.Name
|
||||||
newCell.ID = cell.ID
|
newCell.ID = cell.ID
|
||||||
|
newCell.Type = cell.Type
|
||||||
|
|
||||||
for _, lbl := range labels {
|
for _, lbl := range labels {
|
||||||
if axis, found := cell.Axes[lbl]; !found {
|
if axis, found := cell.Axes[lbl]; !found {
|
||||||
|
|
|
@ -105,6 +105,7 @@ func Test_Service_DashboardCells(t *testing.T) {
|
||||||
W: 4,
|
W: 4,
|
||||||
H: 4,
|
H: 4,
|
||||||
Name: "CPU",
|
Name: "CPU",
|
||||||
|
Type: "bar",
|
||||||
Queries: []chronograf.DashboardQuery{},
|
Queries: []chronograf.DashboardQuery{},
|
||||||
Axes: map[string]chronograf.Axis{},
|
Axes: map[string]chronograf.Axis{},
|
||||||
},
|
},
|
||||||
|
@ -117,6 +118,7 @@ func Test_Service_DashboardCells(t *testing.T) {
|
||||||
W: 4,
|
W: 4,
|
||||||
H: 4,
|
H: 4,
|
||||||
Name: "CPU",
|
Name: "CPU",
|
||||||
|
Type: "bar",
|
||||||
Queries: []chronograf.DashboardQuery{},
|
Queries: []chronograf.DashboardQuery{},
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
|
|
|
@ -50,7 +50,7 @@ type Server struct {
|
||||||
KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"`
|
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"`
|
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."`
|
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"`
|
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"`
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
'no-iterator': 2,
|
'no-iterator': 2,
|
||||||
'no-lone-blocks': 2,
|
'no-lone-blocks': 2,
|
||||||
'no-loop-func': 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-spaces': 2,
|
||||||
'no-multi-str': 2,
|
'no-multi-str': 2,
|
||||||
'no-native-reassign': 2,
|
'no-native-reassign': 2,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
## Packages
|
## Packages
|
||||||
We are using [yarn](https://yarnpkg.com/en/docs/install) 0.19.1.
|
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
|
### Adding new packages
|
||||||
To add a new package, run
|
To add a new package, run
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,15 +9,16 @@
|
||||||
"url": "github:influxdata/chronograf"
|
"url": "github:influxdata/chronograf"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js",
|
"build": "yarn run clean && env NODE_ENV=production webpack --optimize-minimize --config ./webpack/prodConfig.js",
|
||||||
"build:dev": "node_modules/webpack/bin/webpack.js --config ./webpack/devConfig.js",
|
"build:dev": "webpack --config ./webpack/devConfig.js",
|
||||||
"start": "node_modules/webpack/bin/webpack.js -w --config ./webpack/devConfig.js",
|
"start": "webpack --watch --config ./webpack/devConfig.js",
|
||||||
"lint": "node_modules/eslint/bin/eslint.js src/",
|
"lint": "esw src/",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
|
"test:integration": "nightwatch tests --skip",
|
||||||
"test:lint": "yarn run lint; yarn run test",
|
"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",
|
"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"
|
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
@ -45,14 +46,16 @@
|
||||||
"babel-runtime": "^6.5.0",
|
"babel-runtime": "^6.5.0",
|
||||||
"bower": "^1.7.7",
|
"bower": "^1.7.7",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
|
"concurrently": "^3.5.0",
|
||||||
"core-js": "^2.1.3",
|
"core-js": "^2.1.3",
|
||||||
"css-loader": "^0.23.1",
|
"css-loader": "^0.23.1",
|
||||||
"envify": "^3.4.0",
|
"envify": "^3.4.0",
|
||||||
"enzyme": "^2.4.1",
|
"enzyme": "^2.4.1",
|
||||||
"eslint": "3.9.1",
|
"eslint": "^3.14.1",
|
||||||
"eslint-loader": "1.6.1",
|
"eslint-loader": "1.6.1",
|
||||||
"eslint-plugin-prettier": "^2.1.2",
|
"eslint-plugin-prettier": "^2.1.2",
|
||||||
"eslint-plugin-react": "6.6.0",
|
"eslint-plugin-react": "6.6.0",
|
||||||
|
"eslint-watch": "^3.1.2",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"extract-text-webpack-plugin": "^1.0.1",
|
"extract-text-webpack-plugin": "^1.0.1",
|
||||||
"file-loader": "^0.8.5",
|
"file-loader": "^0.8.5",
|
||||||
|
|
|
@ -4,8 +4,8 @@ import _ from 'lodash'
|
||||||
import uuid from 'node-uuid'
|
import uuid from 'node-uuid'
|
||||||
|
|
||||||
import ResizeContainer from 'shared/components/ResizeContainer'
|
import ResizeContainer from 'shared/components/ResizeContainer'
|
||||||
import QueryMaker from 'src/data_explorer/components/QueryMaker'
|
import QueryMaker from 'src/dashboards/components/QueryMaker'
|
||||||
import Visualization from 'src/data_explorer/components/Visualization'
|
import Visualization from 'src/dashboards/components/Visualization'
|
||||||
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
||||||
import DisplayOptions from 'src/dashboards/components/DisplayOptions'
|
import DisplayOptions from 'src/dashboards/components/DisplayOptions'
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class CellEditorOverlay extends Component {
|
||||||
this.handleEditRawText = ::this.handleEditRawText
|
this.handleEditRawText = ::this.handleEditRawText
|
||||||
this.handleSetYAxisBounds = ::this.handleSetYAxisBounds
|
this.handleSetYAxisBounds = ::this.handleSetYAxisBounds
|
||||||
this.handleSetLabel = ::this.handleSetLabel
|
this.handleSetLabel = ::this.handleSetLabel
|
||||||
|
this.getActiveQuery = ::this.getActiveQuery
|
||||||
|
|
||||||
const {cell: {name, type, queries, axes}} = props
|
const {cell: {name, type, queries, axes}} = props
|
||||||
|
|
||||||
|
@ -111,10 +112,17 @@ class CellEditorOverlay extends Component {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddQuery(options) {
|
handleAddQuery() {
|
||||||
const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options)
|
const {queriesWorkingDraft} = this.state
|
||||||
const nextQueries = this.state.queriesWorkingDraft.concat(newQuery)
|
const newIndex = queriesWorkingDraft.length
|
||||||
this.setState({queriesWorkingDraft: nextQueries})
|
|
||||||
|
this.setState({
|
||||||
|
queriesWorkingDraft: [
|
||||||
|
...queriesWorkingDraft,
|
||||||
|
defaultQueryConfig(uuid.v4()),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
this.handleSetActiveQueryIndex(newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteQuery(index) {
|
handleDeleteQuery(index) {
|
||||||
|
@ -167,6 +175,14 @@ class CellEditorOverlay extends Component {
|
||||||
this.setState({activeQueryIndex})
|
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) {
|
async handleEditRawText(url, id, text) {
|
||||||
const templates = removeUnselectedTemplateValues(this.props.templates)
|
const templates = removeUnselectedTemplateValues(this.props.templates)
|
||||||
|
|
||||||
|
@ -203,7 +219,6 @@ class CellEditorOverlay extends Component {
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
const queryActions = {
|
const queryActions = {
|
||||||
addQuery: this.handleAddQuery,
|
|
||||||
editRawTextAsync: this.handleEditRawText,
|
editRawTextAsync: this.handleEditRawText,
|
||||||
..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)),
|
..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)),
|
||||||
}
|
}
|
||||||
|
@ -222,16 +237,14 @@ class CellEditorOverlay extends Component {
|
||||||
initialBottomHeight={INITIAL_HEIGHTS.queryMaker}
|
initialBottomHeight={INITIAL_HEIGHTS.queryMaker}
|
||||||
>
|
>
|
||||||
<Visualization
|
<Visualization
|
||||||
autoRefresh={autoRefresh}
|
axes={axes}
|
||||||
|
type={cellWorkingType}
|
||||||
|
name={cellWorkingName}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
templates={templates}
|
templates={templates}
|
||||||
|
autoRefresh={autoRefresh}
|
||||||
queryConfigs={queriesWorkingDraft}
|
queryConfigs={queriesWorkingDraft}
|
||||||
activeQueryIndex={0}
|
|
||||||
cellType={cellWorkingType}
|
|
||||||
cellName={cellWorkingName}
|
|
||||||
editQueryStatus={editQueryStatus}
|
editQueryStatus={editQueryStatus}
|
||||||
axes={axes}
|
|
||||||
views={[]}
|
|
||||||
/>
|
/>
|
||||||
<CEOBottom>
|
<CEOBottom>
|
||||||
<OverlayControls
|
<OverlayControls
|
||||||
|
@ -256,9 +269,11 @@ class CellEditorOverlay extends Component {
|
||||||
actions={queryActions}
|
actions={queryActions}
|
||||||
autoRefresh={autoRefresh}
|
autoRefresh={autoRefresh}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
|
||||||
onDeleteQuery={this.handleDeleteQuery}
|
onDeleteQuery={this.handleDeleteQuery}
|
||||||
|
onAddQuery={this.handleAddQuery}
|
||||||
activeQueryIndex={activeQueryIndex}
|
activeQueryIndex={activeQueryIndex}
|
||||||
|
activeQuery={this.getActiveQuery()}
|
||||||
|
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
||||||
/>}
|
/>}
|
||||||
</CEOBottom>
|
</CEOBottom>
|
||||||
</ResizeContainer>
|
</ResizeContainer>
|
||||||
|
|
|
@ -38,7 +38,7 @@ class DashboardEditHeader extends Component {
|
||||||
<div className="page-header__container">
|
<div className="page-header__container">
|
||||||
<form
|
<form
|
||||||
className="page-header__left"
|
className="page-header__left"
|
||||||
style={{flex: '1 0 0'}}
|
style={{flex: '1 0 0%'}}
|
||||||
onSubmit={this.handleFormSubmit}
|
onSubmit={this.handleFormSubmit}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
|
||||||
|
import EmptyQuery from 'src/shared/components/EmptyQuery'
|
||||||
|
import QueryTabList from 'src/shared/components/QueryTabList'
|
||||||
|
import QueryTextArea from 'src/dashboards/components/QueryTextArea'
|
||||||
|
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
|
||||||
|
import buildInfluxQLQuery from 'utils/influxql'
|
||||||
|
|
||||||
|
const TEMPLATE_RANGE = {upper: null, lower: ':dashboardTime:'}
|
||||||
|
const rawTextBinder = (links, id, action) => text =>
|
||||||
|
action(links.queries, id, text)
|
||||||
|
const buildText = q =>
|
||||||
|
q.rawText || buildInfluxQLQuery(q.range || TEMPLATE_RANGE, q) || ''
|
||||||
|
|
||||||
|
const QueryMaker = ({
|
||||||
|
source: {links},
|
||||||
|
actions,
|
||||||
|
queries,
|
||||||
|
timeRange,
|
||||||
|
templates,
|
||||||
|
onAddQuery,
|
||||||
|
activeQuery,
|
||||||
|
onDeleteQuery,
|
||||||
|
activeQueryIndex,
|
||||||
|
setActiveQueryIndex,
|
||||||
|
}) =>
|
||||||
|
<div className="query-maker query-maker--panel">
|
||||||
|
<QueryTabList
|
||||||
|
queries={queries}
|
||||||
|
timeRange={timeRange}
|
||||||
|
onAddQuery={onAddQuery}
|
||||||
|
onDeleteQuery={onDeleteQuery}
|
||||||
|
activeQueryIndex={activeQueryIndex}
|
||||||
|
setActiveQueryIndex={setActiveQueryIndex}
|
||||||
|
/>
|
||||||
|
{activeQuery && activeQuery.id
|
||||||
|
? <div className="query-maker--tab-contents">
|
||||||
|
<QueryTextArea
|
||||||
|
query={buildText(activeQuery)}
|
||||||
|
config={activeQuery}
|
||||||
|
onUpdate={rawTextBinder(
|
||||||
|
links,
|
||||||
|
activeQuery.id,
|
||||||
|
actions.editRawTextAsync
|
||||||
|
)}
|
||||||
|
templates={templates}
|
||||||
|
/>
|
||||||
|
<SchemaExplorer
|
||||||
|
query={activeQuery}
|
||||||
|
actions={actions}
|
||||||
|
onAddQuery={onAddQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: <EmptyQuery onAddQuery={onAddQuery} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||||
|
|
||||||
|
QueryMaker.propTypes = {
|
||||||
|
source: shape({
|
||||||
|
links: shape({
|
||||||
|
queries: string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
queries: arrayOf(shape({})).isRequired,
|
||||||
|
timeRange: shape({
|
||||||
|
upper: string,
|
||||||
|
lower: string,
|
||||||
|
}).isRequired,
|
||||||
|
isInDataExplorer: bool,
|
||||||
|
actions: shape({
|
||||||
|
chooseNamespace: func.isRequired,
|
||||||
|
chooseMeasurement: func.isRequired,
|
||||||
|
chooseTag: func.isRequired,
|
||||||
|
groupByTag: func.isRequired,
|
||||||
|
toggleField: func.isRequired,
|
||||||
|
groupByTime: func.isRequired,
|
||||||
|
toggleTagAcceptance: func.isRequired,
|
||||||
|
applyFuncsToField: func.isRequired,
|
||||||
|
editRawTextAsync: func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
setActiveQueryIndex: func.isRequired,
|
||||||
|
onDeleteQuery: func.isRequired,
|
||||||
|
activeQueryIndex: number,
|
||||||
|
activeQuery: shape({}),
|
||||||
|
onAddQuery: func.isRequired,
|
||||||
|
templates: arrayOf(
|
||||||
|
shape({
|
||||||
|
tempVar: string.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QueryMaker
|
|
@ -0,0 +1,263 @@
|
||||||
|
import React, {PropTypes, Component} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
import TemplateDrawer from 'shared/components/TemplateDrawer'
|
||||||
|
import QueryStatus from 'shared/components/QueryStatus'
|
||||||
|
|
||||||
|
import {
|
||||||
|
MATCH_INCOMPLETE_TEMPLATES,
|
||||||
|
applyMasks,
|
||||||
|
insertTempVar,
|
||||||
|
unMask,
|
||||||
|
} from 'src/dashboards/constants'
|
||||||
|
|
||||||
|
class QueryTextArea 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
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.props.query !== nextProps.query) {
|
||||||
|
this.setState({value: nextProps.query})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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') {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({value, isTemplating: false})
|
||||||
|
} else 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})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdate() {
|
||||||
|
this.props.onUpdate(this.state.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChooseTemplate(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
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="query-editor">
|
||||||
|
<textarea
|
||||||
|
className="query-editor--field"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
onBlur={this.handleUpdate}
|
||||||
|
ref={editor => (this.editor = editor)}
|
||||||
|
value={value}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classnames('varmoji', {'varmoji-rotated': isTemplating})}
|
||||||
|
>
|
||||||
|
<div className="varmoji-container">
|
||||||
|
<div className="varmoji-front">
|
||||||
|
<QueryStatus status={status} />
|
||||||
|
</div>
|
||||||
|
<div className="varmoji-back">
|
||||||
|
{isTemplating
|
||||||
|
? <TemplateDrawer
|
||||||
|
onClickTempVar={this.handleClickTempVar}
|
||||||
|
templates={filteredTemplates}
|
||||||
|
selected={selectedTemplate}
|
||||||
|
onMouseOverTempVar={this.handleMouseOverTempVar}
|
||||||
|
handleClickOutside={this.handleCloseDrawer}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
QueryTextArea.propTypes = {
|
||||||
|
query: string.isRequired,
|
||||||
|
onUpdate: func.isRequired,
|
||||||
|
config: shape().isRequired,
|
||||||
|
isInDataExplorer: bool,
|
||||||
|
templates: arrayOf(
|
||||||
|
shape({
|
||||||
|
tempVar: string.isRequired,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QueryTextArea
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import RefreshingGraph from 'shared/components/RefreshingGraph'
|
||||||
|
import buildQueries from 'utils/buildQueriesForGraphs'
|
||||||
|
|
||||||
|
const DashVisualization = (
|
||||||
|
{
|
||||||
|
axes,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
templates,
|
||||||
|
timeRange,
|
||||||
|
autoRefresh,
|
||||||
|
queryConfigs,
|
||||||
|
editQueryStatus,
|
||||||
|
},
|
||||||
|
{source: {links: {proxy}}}
|
||||||
|
) =>
|
||||||
|
<div className="graph">
|
||||||
|
<div className="graph-heading">
|
||||||
|
<div className="graph-title">
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="graph-container">
|
||||||
|
<RefreshingGraph
|
||||||
|
axes={axes}
|
||||||
|
type={type}
|
||||||
|
queries={buildQueries(proxy, queryConfigs, timeRange)}
|
||||||
|
templates={templates}
|
||||||
|
autoRefresh={autoRefresh}
|
||||||
|
editQueryStatus={editQueryStatus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {arrayOf, func, number, shape, string} = PropTypes
|
||||||
|
|
||||||
|
DashVisualization.defaultProps = {
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
DashVisualization.propTypes = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
autoRefresh: number.isRequired,
|
||||||
|
templates: arrayOf(shape()),
|
||||||
|
timeRange: shape({
|
||||||
|
upper: string,
|
||||||
|
lower: string,
|
||||||
|
}).isRequired,
|
||||||
|
queryConfigs: arrayOf(shape({})).isRequired,
|
||||||
|
editQueryStatus: func.isRequired,
|
||||||
|
axes: shape({
|
||||||
|
y: shape({
|
||||||
|
bounds: arrayOf(string),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
DashVisualization.contextTypes = {
|
||||||
|
source: PropTypes.shape({
|
||||||
|
links: PropTypes.shape({
|
||||||
|
proxy: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashVisualization
|
|
@ -64,6 +64,7 @@ const FieldListItem = React.createClass({
|
||||||
active: isSelected,
|
active: isSelected,
|
||||||
})}
|
})}
|
||||||
onClick={_.wrap(fieldFunc, this.handleToggleField)}
|
onClick={_.wrap(fieldFunc, this.handleToggleField)}
|
||||||
|
data-test={`query-builder-list-item-field-${fieldText}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<div className="query-builder--checkbox" />
|
<div className="query-builder--checkbox" />
|
||||||
|
@ -77,6 +78,7 @@ const FieldListItem = React.createClass({
|
||||||
'btn-primary': fieldFunc.funcs.length,
|
'btn-primary': fieldFunc.funcs.length,
|
||||||
})}
|
})}
|
||||||
onClick={this.toggleFunctionsMenu}
|
onClick={this.toggleFunctionsMenu}
|
||||||
|
data-test={`query-builder-list-item-function-${fieldText}`}
|
||||||
>
|
>
|
||||||
{fieldFuncsLabel}
|
{fieldFuncsLabel}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 (
|
|
||||||
<div className="query-maker--tab-contents">
|
|
||||||
<QueryEditor
|
|
||||||
query={q}
|
|
||||||
config={query}
|
|
||||||
onUpdate={this.handleEditRawText}
|
|
||||||
templates={templates}
|
|
||||||
isInDataExplorer={isInDataExplorer}
|
|
||||||
/>
|
|
||||||
{this.renderLists()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className="query-builder--panel">
|
|
||||||
<DatabaseDropdown
|
|
||||||
query={query}
|
|
||||||
onChooseNamespace={this.handleChooseNamespace}
|
|
||||||
/>
|
|
||||||
<div className="query-builder">
|
|
||||||
<MeasurementList
|
|
||||||
query={query}
|
|
||||||
onChooseMeasurement={this.handleChooseMeasurement}
|
|
||||||
onChooseTag={this.handleChooseTag}
|
|
||||||
onToggleTagAcceptance={this.handleToggleTagAcceptance}
|
|
||||||
onGroupByTag={this.handleGroupByTag}
|
|
||||||
/>
|
|
||||||
<FieldList
|
|
||||||
query={query}
|
|
||||||
onToggleField={this.handleToggleField}
|
|
||||||
onGroupByTime={this.handleGroupByTime}
|
|
||||||
applyFuncsToField={this.handleApplyFuncsToField}
|
|
||||||
isInDataExplorer={isInDataExplorer}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="query-builder">
|
|
||||||
<DatabaseList
|
|
||||||
query={query}
|
|
||||||
onChooseNamespace={this.handleChooseNamespace}
|
|
||||||
/>
|
|
||||||
<MeasurementList
|
|
||||||
query={query}
|
|
||||||
onChooseMeasurement={this.handleChooseMeasurement}
|
|
||||||
onChooseTag={this.handleChooseTag}
|
|
||||||
onToggleTagAcceptance={this.handleToggleTagAcceptance}
|
|
||||||
onGroupByTag={this.handleGroupByTag}
|
|
||||||
/>
|
|
||||||
<FieldList
|
|
||||||
query={query}
|
|
||||||
onToggleField={this.handleToggleField}
|
|
||||||
onGroupByTime={this.handleGroupByTime}
|
|
||||||
applyFuncsToField={this.handleApplyFuncsToField}
|
|
||||||
isInDataExplorer={isInDataExplorer}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default QueryBuilder
|
|
|
@ -1,40 +1,20 @@
|
||||||
import React, {PropTypes, Component} from 'react'
|
import React, {PropTypes, Component} from 'react'
|
||||||
import _ from 'lodash'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
import Dropdown from 'shared/components/Dropdown'
|
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 {QUERY_TEMPLATES} from 'src/data_explorer/constants'
|
||||||
import {
|
import QueryStatus from 'shared/components/QueryStatus'
|
||||||
MATCH_INCOMPLETE_TEMPLATES,
|
|
||||||
applyMasks,
|
|
||||||
insertTempVar,
|
|
||||||
unMask,
|
|
||||||
} from 'src/dashboards/constants'
|
|
||||||
|
|
||||||
class QueryEditor extends Component {
|
class QueryEditor extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
value: this.props.query,
|
value: this.props.query,
|
||||||
isTemplating: false,
|
|
||||||
selectedTemplate: {
|
|
||||||
tempVar: _.get(this.props.templates, ['0', 'tempVar'], ''),
|
|
||||||
},
|
|
||||||
filteredTemplates: this.props.templates,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleKeyDown = ::this.handleKeyDown
|
this.handleKeyDown = ::this.handleKeyDown
|
||||||
this.handleChange = ::this.handleChange
|
this.handleChange = ::this.handleChange
|
||||||
this.handleUpdate = ::this.handleUpdate
|
this.handleUpdate = ::this.handleUpdate
|
||||||
this.handleChooseTemplate = ::this.handleChooseTemplate
|
this.handleChooseMetaQuery = ::this.handleChooseMetaQuery
|
||||||
this.handleCloseDrawer = ::this.handleCloseDrawer
|
|
||||||
this.findTempVar = ::this.findTempVar
|
|
||||||
this.handleTemplateReplace = ::this.handleTemplateReplace
|
|
||||||
this.handleMouseOverTempVar = ::this.handleMouseOverTempVar
|
|
||||||
this.handleClickTempVar = ::this.handleClickTempVar
|
|
||||||
this.closeDrawer = ::this.closeDrawer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
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) {
|
handleKeyDown(e) {
|
||||||
const {isTemplating, value} = this.state
|
const {value} = this.state
|
||||||
|
|
||||||
if (isTemplating) {
|
if (e.key === 'Escape') {
|
||||||
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') {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({value, isTemplating: false})
|
this.setState({value})
|
||||||
} else if (e.key === 'Enter') {
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.handleUpdate()
|
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() {
|
handleChange() {
|
||||||
const {templates} = this.props
|
this.setState({value: this.editor.value})
|
||||||
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})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdate() {
|
handleUpdate() {
|
||||||
this.props.onUpdate(this.state.value)
|
this.props.onUpdate(this.state.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChooseTemplate(template) {
|
handleChooseMetaQuery(template) {
|
||||||
this.setState({value: template.query})
|
this.setState({value: template.query})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectTempVar(tempVar) {
|
|
||||||
this.setState({selectedTemplate: tempVar})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {config: {status}} = this.props
|
const {config: {status}} = this.props
|
||||||
const {
|
const {value} = this.state
|
||||||
value,
|
|
||||||
isTemplating,
|
|
||||||
selectedTemplate,
|
|
||||||
filteredTemplates,
|
|
||||||
} = this.state
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="query-editor">
|
<div className="query-editor">
|
||||||
|
@ -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..."
|
placeholder="Enter a query or select database, measurement, and field below and have us build one for you..."
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
|
data-test="query-editor-field"
|
||||||
/>
|
/>
|
||||||
<div
|
<div className="varmoji">
|
||||||
className={classnames('varmoji', {'varmoji-rotated': isTemplating})}
|
|
||||||
>
|
|
||||||
<div className="varmoji-container">
|
<div className="varmoji-container">
|
||||||
<div className="varmoji-front">
|
<div className="varmoji-front">
|
||||||
{this.renderStatus(status)}
|
<QueryStatus status={status}>
|
||||||
</div>
|
<Dropdown
|
||||||
<div className="varmoji-back">
|
items={QUERY_TEMPLATES}
|
||||||
{isTemplating
|
selected={'Query Templates'}
|
||||||
? <TemplateDrawer
|
onChoose={this.handleChooseMetaQuery}
|
||||||
onClickTempVar={this.handleClickTempVar}
|
className="dropdown-140 query-editor--templates"
|
||||||
templates={filteredTemplates}
|
buttonSize="btn-xs"
|
||||||
selected={selectedTemplate}
|
/>
|
||||||
onMouseOverTempVar={this.handleMouseOverTempVar}
|
</QueryStatus>
|
||||||
handleClickOutside={this.handleCloseDrawer}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStatus(status) {
|
|
||||||
const {isInDataExplorer} = this.props
|
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
return (
|
|
||||||
<div className="query-editor--status">
|
|
||||||
{isInDataExplorer
|
|
||||||
? <Dropdown
|
|
||||||
items={QUERY_TEMPLATES}
|
|
||||||
selected={'Query Templates'}
|
|
||||||
onChoose={this.handleChooseTemplate}
|
|
||||||
className="dropdown-140 query-editor--templates"
|
|
||||||
buttonSize="btn-xs"
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.loading) {
|
|
||||||
return (
|
|
||||||
<div className="query-editor--status">
|
|
||||||
<LoadingDots />
|
|
||||||
{isInDataExplorer
|
|
||||||
? <Dropdown
|
|
||||||
items={QUERY_TEMPLATES}
|
|
||||||
selected={'Query Templates'}
|
|
||||||
onChoose={this.handleChooseTemplate}
|
|
||||||
className="dropdown-140 query-editor--templates"
|
|
||||||
buttonSize="btn-xs"
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="query-editor--status">
|
|
||||||
<span
|
|
||||||
className={classnames('query-status-output', {
|
|
||||||
'query-status-output--error': status.error,
|
|
||||||
'query-status-output--success': status.success,
|
|
||||||
'query-status-output--warning': status.warn,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classnames('icon', {
|
|
||||||
stop: status.error,
|
|
||||||
checkmark: status.success,
|
|
||||||
'alert-triangle': status.warn,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{status.error || status.warn || status.success}
|
|
||||||
</span>
|
|
||||||
{isInDataExplorer
|
|
||||||
? <Dropdown
|
|
||||||
items={QUERY_TEMPLATES}
|
|
||||||
selected={'Query Templates'}
|
|
||||||
onChoose={this.handleChooseTemplate}
|
|
||||||
className="dropdown-140 query-editor--templates"
|
|
||||||
buttonSize="btn-xs"
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
const {func, shape, string} = PropTypes
|
||||||
|
|
||||||
QueryEditor.propTypes = {
|
QueryEditor.propTypes = {
|
||||||
query: string.isRequired,
|
query: string.isRequired,
|
||||||
onUpdate: func.isRequired,
|
onUpdate: func.isRequired,
|
||||||
config: shape().isRequired,
|
config: shape().isRequired,
|
||||||
isInDataExplorer: bool,
|
|
||||||
templates: arrayOf(
|
|
||||||
shape({
|
|
||||||
tempVar: string.isRequired,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QueryEditor
|
export default QueryEditor
|
||||||
|
|
|
@ -1,181 +1,87 @@
|
||||||
import React, {PropTypes} from 'react'
|
import React, {PropTypes} from 'react'
|
||||||
|
|
||||||
import QueryBuilder from './QueryBuilder'
|
import QueryEditor from './QueryEditor'
|
||||||
import QueryMakerTab from './QueryMakerTab'
|
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 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({
|
const buildText = (q, timeRange) =>
|
||||||
propTypes: {
|
q.rawText || buildInfluxQLQuery(q.range || timeRange, q) || ''
|
||||||
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,
|
|
||||||
},
|
|
||||||
|
|
||||||
handleAddQuery() {
|
const QueryMaker = ({
|
||||||
const newIndex = this.props.queries.length
|
source,
|
||||||
this.props.actions.addQuery()
|
actions,
|
||||||
this.props.setActiveQueryIndex(newIndex)
|
queries,
|
||||||
},
|
timeRange,
|
||||||
|
onAddQuery,
|
||||||
handleAddRawQuery() {
|
activeQuery,
|
||||||
const newIndex = this.props.queries.length
|
onDeleteQuery,
|
||||||
this.props.actions.addQuery({rawText: ''})
|
activeQueryIndex,
|
||||||
this.props.setActiveQueryIndex(newIndex)
|
setActiveQueryIndex,
|
||||||
},
|
}) =>
|
||||||
|
<div className="query-maker query-maker--panel">
|
||||||
getActiveQuery() {
|
<QueryTabList
|
||||||
const {queries, activeQueryIndex} = this.props
|
queries={queries}
|
||||||
const activeQuery = queries[activeQueryIndex]
|
timeRange={timeRange}
|
||||||
const defaultQuery = queries[0]
|
onAddQuery={onAddQuery}
|
||||||
|
onDeleteQuery={onDeleteQuery}
|
||||||
return activeQuery || defaultQuery
|
activeQueryIndex={activeQueryIndex}
|
||||||
},
|
setActiveQueryIndex={setActiveQueryIndex}
|
||||||
|
/>
|
||||||
render() {
|
{activeQuery && activeQuery.id
|
||||||
const {height, top, layout} = this.props
|
? <div className="query-maker--tab-contents">
|
||||||
return (
|
<QueryEditor
|
||||||
<div
|
query={buildText(activeQuery, timeRange)}
|
||||||
className={classnames('query-maker', {
|
config={activeQuery}
|
||||||
'query-maker--panel': layout === 'panel',
|
onUpdate={rawTextBinder(
|
||||||
})}
|
source.links,
|
||||||
style={{height, top}}
|
activeQuery.id,
|
||||||
>
|
actions.editRawTextAsync
|
||||||
{this.renderQueryTabList()}
|
)}
|
||||||
{this.renderQueryBuilder()}
|
/>
|
||||||
</div>
|
<SchemaExplorer
|
||||||
)
|
query={activeQuery}
|
||||||
},
|
actions={actions}
|
||||||
|
onAddQuery={onAddQuery}
|
||||||
renderQueryBuilder() {
|
/>
|
||||||
const {
|
|
||||||
timeRange,
|
|
||||||
actions,
|
|
||||||
source,
|
|
||||||
templates,
|
|
||||||
layout,
|
|
||||||
isInDataExplorer,
|
|
||||||
} = this.props
|
|
||||||
const query = this.getActiveQuery()
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
return (
|
|
||||||
<div className="query-maker--empty">
|
|
||||||
<h5>This Graph has no Queries</h5>
|
|
||||||
<br />
|
|
||||||
<div
|
|
||||||
className="btn btn-primary"
|
|
||||||
role="button"
|
|
||||||
onClick={this.handleAddQuery}
|
|
||||||
>
|
|
||||||
Add a Query
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
: <EmptyQuery onAddQuery={onAddQuery} />}
|
||||||
}
|
</div>
|
||||||
|
|
||||||
// NOTE
|
const {arrayOf, func, number, shape, string} = PropTypes
|
||||||
// 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
|
|
||||||
|
|
||||||
return (
|
QueryMaker.propTypes = {
|
||||||
<QueryBuilder
|
source: shape({
|
||||||
source={source}
|
links: shape({
|
||||||
timeRange={timeRange}
|
queries: string.isRequired,
|
||||||
templates={templates}
|
}).isRequired,
|
||||||
query={query}
|
}).isRequired,
|
||||||
actions={actions}
|
queries: arrayOf(shape({})).isRequired,
|
||||||
onAddQuery={this.handleAddQuery}
|
timeRange: shape({
|
||||||
layout={layout}
|
upper: string,
|
||||||
isInDataExplorer={isInDataExplorer}
|
lower: string,
|
||||||
/>
|
}).isRequired,
|
||||||
)
|
actions: shape({
|
||||||
},
|
chooseNamespace: func.isRequired,
|
||||||
|
chooseMeasurement: func.isRequired,
|
||||||
renderQueryTabList() {
|
chooseTag: func.isRequired,
|
||||||
const {
|
groupByTag: func.isRequired,
|
||||||
queries,
|
addQuery: func.isRequired,
|
||||||
activeQueryIndex,
|
toggleField: func.isRequired,
|
||||||
onDeleteQuery,
|
groupByTime: func.isRequired,
|
||||||
timeRange,
|
toggleTagAcceptance: func.isRequired,
|
||||||
setActiveQueryIndex,
|
applyFuncsToField: func.isRequired,
|
||||||
} = this.props
|
editRawTextAsync: func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
return (
|
setActiveQueryIndex: func.isRequired,
|
||||||
<div className="query-maker--tabs">
|
onDeleteQuery: func.isRequired,
|
||||||
{queries.map((q, i) => {
|
onAddQuery: func.isRequired,
|
||||||
return (
|
activeQuery: shape({}),
|
||||||
<QueryMakerTab
|
activeQueryIndex: number,
|
||||||
isActive={i === activeQueryIndex}
|
|
||||||
key={i}
|
|
||||||
queryIndex={i}
|
|
||||||
query={q}
|
|
||||||
onSelect={setActiveQueryIndex}
|
|
||||||
onDelete={onDeleteQuery}
|
|
||||||
queryTabText={
|
|
||||||
q.rawText ||
|
|
||||||
buildInfluxQLQuery(timeRange, q) ||
|
|
||||||
`Query ${i + 1}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{this.props.children}
|
|
||||||
<div
|
|
||||||
className="query-maker--new btn btn-sm btn-primary"
|
|
||||||
onClick={this.handleAddQuery}
|
|
||||||
>
|
|
||||||
<span className="icon plus" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
QueryMaker.defaultProps = {
|
|
||||||
layout: 'default',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QueryMaker
|
export default QueryMaker
|
||||||
|
|
|
@ -33,7 +33,11 @@ const QueryMakerTab = React.createClass({
|
||||||
<label>
|
<label>
|
||||||
{this.props.queryTabText}
|
{this.props.queryTabText}
|
||||||
</label>
|
</label>
|
||||||
<span className="query-maker--delete" onClick={this.handleDelete} />
|
<span
|
||||||
|
className="query-maker--delete"
|
||||||
|
onClick={this.handleDelete}
|
||||||
|
data-test="query-maker-delete"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -76,7 +76,12 @@ const TagListItem = React.createClass({
|
||||||
active: selectedTagValues.indexOf(v) > -1,
|
active: selectedTagValues.indexOf(v) > -1,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<div className={cx} onClick={_.wrap(v, this.handleChoose)} key={v}>
|
<div
|
||||||
|
className={cx}
|
||||||
|
onClick={_.wrap(v, this.handleChoose)}
|
||||||
|
key={v}
|
||||||
|
data-test={`query-builder-list-item-tag-value-${v}`}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<div className="query-builder--checkbox" />
|
<div className="query-builder--checkbox" />
|
||||||
{v}
|
{v}
|
||||||
|
@ -103,6 +108,7 @@ const TagListItem = React.createClass({
|
||||||
<div
|
<div
|
||||||
className={classnames('query-builder--list-item', {active: isOpen})}
|
className={classnames('query-builder--list-item', {active: isOpen})}
|
||||||
onClick={this.handleClickKey}
|
onClick={this.handleClickKey}
|
||||||
|
data-test={`query-builder-list-item-tag-${tagKey}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<div className="query-builder--caret icon caret-right" />
|
<div className="query-builder--caret icon caret-right" />
|
||||||
|
|
|
@ -11,6 +11,7 @@ const VisHeader = ({views, view, onToggleView, name}) =>
|
||||||
key={v}
|
key={v}
|
||||||
onClick={() => onToggleView(v)}
|
onClick={() => onToggleView(v)}
|
||||||
className={classnames({active: view === v})}
|
className={classnames({active: view === v})}
|
||||||
|
data-test={`data-${v}`}
|
||||||
>
|
>
|
||||||
{_.upperFirst(v)}
|
{_.upperFirst(v)}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -24,6 +24,7 @@ const WriteDataBody = ({
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onChange={handleEdit}
|
onChange={handleEdit}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
data-test="manual-entry-field"
|
||||||
/>
|
/>
|
||||||
: <div className="write-data-form--file">
|
: <div className="write-data-form--file">
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -37,6 +37,7 @@ const WriteDataFooter = ({
|
||||||
(!uploadContent && !isManual) ||
|
(!uploadContent && !isManual) ||
|
||||||
isUploading
|
isUploading
|
||||||
}
|
}
|
||||||
|
data-test="write-data-submit-button"
|
||||||
>
|
>
|
||||||
Write
|
Write
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -27,6 +27,7 @@ const WriteDataHeader = ({
|
||||||
<li
|
<li
|
||||||
onClick={() => toggleWriteView(true)}
|
onClick={() => toggleWriteView(true)}
|
||||||
className={isManual ? 'active' : ''}
|
className={isManual ? 'active' : ''}
|
||||||
|
data-test="manual-entry-button"
|
||||||
>
|
>
|
||||||
Manual Entry
|
Manual Entry
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {PropTypes} from 'react'
|
import React, {PropTypes, Component} from 'react'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from '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 * as dataExplorerActionCreators from 'src/data_explorer/actions/view'
|
||||||
import {writeLineProtocolAsync} from 'src/data_explorer/actions/view/write'
|
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({
|
this.state = {
|
||||||
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 {
|
|
||||||
activeQueryIndex: 0,
|
activeQueryIndex: 0,
|
||||||
showWriteForm: false,
|
showWriteForm: false,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
|
getChildContext() {
|
||||||
|
return {source: this.props.source}
|
||||||
|
}
|
||||||
|
|
||||||
handleSetActiveQueryIndex(index) {
|
handleSetActiveQueryIndex(index) {
|
||||||
this.setState({activeQueryIndex: index})
|
this.setState({activeQueryIndex: index})
|
||||||
},
|
}
|
||||||
|
|
||||||
handleDeleteQuery(index) {
|
handleDeleteQuery(index) {
|
||||||
const {queryConfigs} = this.props
|
const {queryConfigs, queryConfigActions} = this.props
|
||||||
const query = queryConfigs[index]
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -89,6 +69,7 @@ const DataExplorer = React.createClass({
|
||||||
source,
|
source,
|
||||||
writeLineProtocol,
|
writeLineProtocol,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const {activeQueryIndex, showWriteForm} = this.state
|
const {activeQueryIndex, showWriteForm} = this.state
|
||||||
const selectedDatabase = _.get(
|
const selectedDatabase = _.get(
|
||||||
queryConfigs,
|
queryConfigs,
|
||||||
|
@ -128,10 +109,11 @@ const DataExplorer = React.createClass({
|
||||||
actions={queryConfigActions}
|
actions={queryConfigActions}
|
||||||
autoRefresh={autoRefresh}
|
autoRefresh={autoRefresh}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
isInDataExplorer={true}
|
setActiveQueryIndex={::this.handleSetActiveQueryIndex}
|
||||||
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
onDeleteQuery={::this.handleDeleteQuery}
|
||||||
onDeleteQuery={this.handleDeleteQuery}
|
onAddQuery={::this.handleAddQuery}
|
||||||
activeQueryIndex={activeQueryIndex}
|
activeQueryIndex={activeQueryIndex}
|
||||||
|
activeQuery={::this.getActiveQuery()}
|
||||||
/>
|
/>
|
||||||
<Visualization
|
<Visualization
|
||||||
isInDataExplorer={true}
|
isInDataExplorer={true}
|
||||||
|
@ -145,10 +127,47 @@ const DataExplorer = React.createClass({
|
||||||
</ResizeContainer>
|
</ResizeContainer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
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 {
|
const {
|
||||||
app: {persisted: {autoRefresh}},
|
app: {persisted: {autoRefresh}},
|
||||||
dataExplorer,
|
dataExplorer,
|
||||||
|
@ -165,7 +184,7 @@ function mapStateToProps(state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
|
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
|
||||||
errorThrownAction: bindActionCreators(errorThrown, dispatch),
|
errorThrownAction: bindActionCreators(errorThrown, dispatch),
|
||||||
|
|
|
@ -49,7 +49,11 @@ const Header = React.createClass({
|
||||||
<div className="page-header__right">
|
<div className="page-header__right">
|
||||||
<GraphTips />
|
<GraphTips />
|
||||||
<SourceIndicator sourceName={this.context.source.name} />
|
<SourceIndicator sourceName={this.context.source.name} />
|
||||||
<div className="btn btn-sm btn-default" onClick={showWriteForm}>
|
<div
|
||||||
|
className="btn btn-sm btn-default"
|
||||||
|
onClick={showWriteForm}
|
||||||
|
data-test="write-data-button"
|
||||||
|
>
|
||||||
<span className="icon pencil" />
|
<span className="icon pencil" />
|
||||||
Write Data
|
Write Data
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 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("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 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"`,
|
SHOW TAG VALUES FROM /win_system|system/ WITH KEY = "host"`,
|
||||||
db: telegrafDB,
|
db: telegrafDB,
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
|
@ -72,9 +72,11 @@ export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
|
||||||
})
|
})
|
||||||
|
|
||||||
winUptimeSeries.forEach(s => {
|
winUptimeSeries.forEach(s => {
|
||||||
const uptimeIndex = s.columns.findIndex(col => col === 'deltaUptime')
|
const winUptimeIndex = s.columns.findIndex(
|
||||||
hosts[s.tags.host].deltaUptime =
|
col => col === 'winDeltaUptime'
|
||||||
s.values[s.values.length - 1][uptimeIndex]
|
)
|
||||||
|
hosts[s.tags.host].winDeltaUptime =
|
||||||
|
s.values[s.values.length - 1][winUptimeIndex]
|
||||||
})
|
})
|
||||||
|
|
||||||
return hosts
|
return hosts
|
||||||
|
|
|
@ -209,7 +209,9 @@ const HostRow = React.createClass({
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'table-dot',
|
'table-dot',
|
||||||
host.deltaUptime > 0 ? 'dot-success' : 'dot-critical'
|
Math.max(host.deltaUptime || 0, host.winDeltaUptime || 0) > 0
|
||||||
|
? 'dot-success'
|
||||||
|
: 'dot-critical'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'babel-polyfill'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {render} from 'react-dom'
|
import {render} from 'react-dom'
|
||||||
import {Provider} from 'react-redux'
|
import {Provider} from 'react-redux'
|
||||||
|
@ -47,7 +49,8 @@ const errorsQueue = []
|
||||||
|
|
||||||
const rootNode = document.getElementById('react-root')
|
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
|
window.basepath = basepath
|
||||||
const browserHistory = useRouterHistory(createHistory)({
|
const browserHistory = useRouterHistory(createHistory)({
|
||||||
basename: basepath, // this is written in when available by the URL prefixer middleware
|
basename: basepath, // this is written in when available by the URL prefixer middleware
|
||||||
|
|
|
@ -2,9 +2,9 @@ import React, {PropTypes} from 'react'
|
||||||
import buildInfluxQLQuery from 'utils/influxql'
|
import buildInfluxQLQuery from 'utils/influxql'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import DatabaseList from '../../data_explorer/components/DatabaseList'
|
import DatabaseList from 'src/shared/components/DatabaseList'
|
||||||
import MeasurementList from '../../data_explorer/components/MeasurementList'
|
import MeasurementList from 'src/shared/components/MeasurementList'
|
||||||
import FieldList from '../../data_explorer/components/FieldList'
|
import FieldList from 'src/shared/components/FieldList'
|
||||||
|
|
||||||
import {defaultEveryFrequency} from 'src/kapacitor/constants'
|
import {defaultEveryFrequency} from 'src/kapacitor/constants'
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class RuleMessageOptions extends Component {
|
||||||
<input
|
<input
|
||||||
id="alert-input"
|
id="alert-input"
|
||||||
className="form-control input-sm form-malachite"
|
className="form-control input-sm form-malachite"
|
||||||
style={{flex: '1 0 0'}}
|
style={{flex: '1 0 0%'}}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={args.placeholder}
|
placeholder={args.placeholder}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
|
@ -83,7 +83,7 @@ class RuleMessageOptions extends Component {
|
||||||
className="form-control input-sm form-malachite"
|
className="form-control input-sm form-malachite"
|
||||||
style={{
|
style={{
|
||||||
margin: '0 15px 0 5px',
|
margin: '0 15px 0 5px',
|
||||||
flex: '1 0 0',
|
flex: '1 0 0%',
|
||||||
}}
|
}}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|
|
@ -215,7 +215,7 @@ const AutoRefresh = ComposedComponent => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="graph-empty">
|
<div className="graph-empty">
|
||||||
<p>No Results</p>
|
<p data-test="data-explorer-no-results">No Results</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -83,6 +83,7 @@ const DatabaseList = React.createClass({
|
||||||
})}
|
})}
|
||||||
key={`${database}..${retentionPolicy}`}
|
key={`${database}..${retentionPolicy}`}
|
||||||
onClick={_.wrap(namespace, onChooseNamespace)}
|
onClick={_.wrap(namespace, onChooseNamespace)}
|
||||||
|
data-test={`query-builder-list-item-database-${database}`}
|
||||||
>
|
>
|
||||||
{database}.{retentionPolicy}
|
{database}.{retentionPolicy}
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
|
||||||
|
const EmptyQueryState = ({onAddQuery}) =>
|
||||||
|
<div className="query-maker--empty">
|
||||||
|
<h5>This Graph has no Queries</h5>
|
||||||
|
<br />
|
||||||
|
<div className="btn btn-primary" onClick={onAddQuery}>
|
||||||
|
Add a Query
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {func} = PropTypes
|
||||||
|
|
||||||
|
EmptyQueryState.propTypes = {
|
||||||
|
onAddQuery: func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmptyQueryState
|
|
@ -74,6 +74,7 @@ class FunctionSelector extends Component {
|
||||||
<div
|
<div
|
||||||
className="btn btn-xs btn-success"
|
className="btn btn-xs btn-success"
|
||||||
onClick={this.handleApplyFunctions}
|
onClick={this.handleApplyFunctions}
|
||||||
|
data-test="function-selector-apply"
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,6 +91,7 @@ class FunctionSelector extends Component {
|
||||||
f,
|
f,
|
||||||
singleSelect ? this.onSingleSelect : this.onSelect
|
singleSelect ? this.onSingleSelect : this.onSelect
|
||||||
)}
|
)}
|
||||||
|
data-test={`function-selector-item-${f}`}
|
||||||
>
|
>
|
||||||
{f}
|
{f}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -127,7 +127,7 @@ class LayoutRenderer extends Component {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="graph-empty">
|
<div className="graph-empty">
|
||||||
<p>No Results</p>
|
<p data-test="data-explorer-no-results">No Results</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,7 @@ const MeasurementList = React.createClass({
|
||||||
className={classnames('query-builder--list-item', {
|
className={classnames('query-builder--list-item', {
|
||||||
active: isActive,
|
active: isActive,
|
||||||
})}
|
})}
|
||||||
|
data-test={`query-builder-list-item-measurement-${measurement}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<div className="query-builder--caret icon caret-right" />
|
<div className="query-builder--caret icon caret-right" />
|
|
@ -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 <div className="query-editor--status" />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.loading) {
|
||||||
|
return (
|
||||||
|
<div className="query-editor--status">
|
||||||
|
<LoadingDots />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="query-editor--status">
|
||||||
|
<span
|
||||||
|
className={classnames('query-status-output', {
|
||||||
|
'query-status-output--error': status.error,
|
||||||
|
'query-status-output--success': status.success,
|
||||||
|
'query-status-output--warning': status.warn,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classnames('icon', {
|
||||||
|
stop: status.error,
|
||||||
|
checkmark: status.success,
|
||||||
|
'alert-triangle': status.warn,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{status.error || status.warn || status.success}
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {node, shape, string} = PropTypes
|
||||||
|
|
||||||
|
QueryStatus.propTypes = {
|
||||||
|
status: shape({
|
||||||
|
error: string,
|
||||||
|
success: string,
|
||||||
|
warn: string,
|
||||||
|
}),
|
||||||
|
children: node,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QueryStatus
|
|
@ -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,
|
||||||
|
}) =>
|
||||||
|
<div className="query-maker--tabs">
|
||||||
|
{queries.map((q, i) =>
|
||||||
|
<QueryMakerTab
|
||||||
|
isActive={i === activeQueryIndex}
|
||||||
|
key={i}
|
||||||
|
queryIndex={i}
|
||||||
|
query={q}
|
||||||
|
onSelect={setActiveQueryIndex}
|
||||||
|
onDelete={onDeleteQuery}
|
||||||
|
queryTabText={
|
||||||
|
q.rawText || buildInfluxQLQuery(timeRange, q) || `Query ${i + 1}`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="query-maker--new btn btn-sm btn-primary"
|
||||||
|
onClick={onAddQuery}
|
||||||
|
>
|
||||||
|
<span className="icon plus" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}) =>
|
||||||
|
<div className="query-builder">
|
||||||
|
<DatabaseList
|
||||||
|
query={query}
|
||||||
|
onChooseNamespace={actionBinder(id, chooseNamespace)}
|
||||||
|
/>
|
||||||
|
<MeasurementList
|
||||||
|
query={query}
|
||||||
|
onChooseTag={actionBinder(id, chooseTag)}
|
||||||
|
onGroupByTag={actionBinder(id, groupByTag)}
|
||||||
|
onChooseMeasurement={actionBinder(id, chooseMeasurement)}
|
||||||
|
onToggleTagAcceptance={actionBinder(id, toggleTagAcceptance)}
|
||||||
|
/>
|
||||||
|
<FieldList
|
||||||
|
query={query}
|
||||||
|
onToggleField={actionBinder(id, toggleFieldWithGroupByInterval)}
|
||||||
|
onGroupByTime={actionBinder(id, groupByTime)}
|
||||||
|
applyFuncsToField={actionBinder(id, applyFuncsToField)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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
|
|
@ -7,7 +7,10 @@ export default function resizeLayout() {
|
||||||
action.type === 'ENABLE_PRESENTATION_MODE' ||
|
action.type === 'ENABLE_PRESENTATION_MODE' ||
|
||||||
action.type === 'DISABLE_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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,10 +161,10 @@
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $g13-mist;
|
color: $g13-mist;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.dygraph-legend--filter {
|
.dygraph-legend--filter {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.dygraph-legend--divider {
|
.dygraph-legend--divider {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
.query-builder {
|
.query-builder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
@ -13,10 +13,10 @@
|
||||||
.query-builder--column {
|
.query-builder--column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 2 0 0;
|
flex: 2 0 0%;
|
||||||
}
|
}
|
||||||
.query-builder--column-db {
|
.query-builder--column-db {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.query-builder--heading {
|
.query-builder--heading {
|
||||||
@include no-user-select();
|
@include no-user-select();
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
.query-builder--list,
|
.query-builder--list,
|
||||||
.query-builder--list-empty {
|
.query-builder--list-empty {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.query-builder--list {
|
.query-builder--list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
}
|
}
|
||||||
/* Filter Element */
|
/* Filter Element */
|
||||||
.query-builder--filter {
|
.query-builder--filter {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.query-status-output {
|
.query-status-output {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: $query-editor--status-default;
|
color: $query-editor--status-default;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -94,7 +94,7 @@ $query-editor-tab-active: $g3-castle;
|
||||||
height: $query-maker--tabs-height;
|
height: $query-maker--tabs-height;
|
||||||
margin: 0 2px 0 0;
|
margin: 0 2px 0 0;
|
||||||
max-width: $query-maker--tab-width;
|
max-width: $query-maker--tab-width;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -170,7 +170,7 @@ $query-editor-tab-active: $g3-castle;
|
||||||
}
|
}
|
||||||
.query-maker--tab-contents,
|
.query-maker--tab-contents,
|
||||||
.query-maker--empty {
|
.query-maker--empty {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
margin: 0 0 $query-maker--gutter 0;
|
margin: 0 0 $query-maker--gutter 0;
|
||||||
background-color: $query-maker--tab-contents-bg;
|
background-color: $query-maker--tab-contents-bg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,7 @@ $table-tab-scrollbar-height: 6px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
> .panel-body {flex: 1 0 0;}
|
> .panel-body {flex: 1 0 0%;}
|
||||||
.generic-empty-state {height: 100%;}
|
.generic-empty-state {height: 100%;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ $table-tab-scrollbar-height: 6px;
|
||||||
color: $g17-whisper;
|
color: $g17-whisper;
|
||||||
}
|
}
|
||||||
.alert-history-table--tbody {
|
.alert-history-table--tbody {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.alert-history-table--tr {
|
.alert-history-table--tr {
|
||||||
|
|
|
@ -42,7 +42,7 @@ button.btn.template-control--manage {
|
||||||
}
|
}
|
||||||
.template-control--controls {
|
.template-control--controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.template-control--empty {
|
.template-control--empty {
|
||||||
|
@ -64,7 +64,7 @@ button.btn.template-control--manage {
|
||||||
.dropdown {
|
.dropdown {
|
||||||
order: 2;
|
order: 2;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
border-radius: 0 0 $radius-small $radius-small;
|
border-radius: 0 0 $radius-small $radius-small;
|
||||||
|
|
|
@ -166,7 +166,7 @@ $tvmp-table-gutter: 8px;
|
||||||
> *:last-child {margin-right: 0;}
|
> *:last-child {margin-right: 0;}
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
|
|
||||||
& > .dropdown-toggle {width: 100%;}
|
& > .dropdown-toggle {width: 100%;}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ table.table-highlight > tbody > tr.admin-table--edit-row:hover {
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
margin: 0 4px 0 0;
|
margin: 0 4px 0 0;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pre.admin-table--query {
|
pre.admin-table--query {
|
||||||
|
@ -118,7 +118,7 @@ pre.admin-table--query {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> .form-control {
|
> .form-control {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ pre.admin-table--query {
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
margin: 0 8px 0 0;
|
margin: 0 8px 0 0;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ $config-endpoint-tab-bg-active: $g3-castle;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.config-endpoint--tabs {
|
.config-endpoint--tabs {
|
||||||
flex: 0 0 0;
|
flex: 0 0 0%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.btn-group.tab-group {
|
.btn-group.tab-group {
|
||||||
|
@ -27,7 +27,7 @@ $config-endpoint-tab-bg-active: $g3-castle;
|
||||||
border-radius: $radius 0 0 $radius;
|
border-radius: $radius 0 0 $radius;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ $config-endpoint-tab-bg-active: $g3-castle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.config-endpoint--tab-contents {
|
.config-endpoint--tab-contents {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
background-color: $config-endpoint-tab-bg-active;
|
background-color: $config-endpoint-tab-bg-active;
|
||||||
border-radius: 0 $radius $radius 0;
|
border-radius: 0 $radius $radius 0;
|
||||||
padding: 16px 42px;
|
padding: 16px 42px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ $overlay-z: 100;
|
||||||
margin: 0 15%;
|
margin: 0 15%;
|
||||||
}
|
}
|
||||||
.overlay-technology .query-maker {
|
.overlay-technology .query-maker {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
padding: 0 18px;
|
padding: 0 18px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: $g2-kevlar;
|
background-color: $g2-kevlar;
|
||||||
|
|
|
@ -3887,7 +3887,7 @@ p .label {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.dropdown-toggle.btn-xs .caret {
|
.dropdown-toggle.btn-xs .caret {
|
||||||
right: 7px;
|
right: 7px;
|
||||||
|
@ -4041,7 +4041,7 @@ p .label {
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: color .25s ease;
|
transition: color .25s ease;
|
||||||
|
|
||||||
flex: 1 0 0;
|
flex: 1 0 0%;
|
||||||
}
|
}
|
||||||
.dropdown-menu li.dropdown-item > a,
|
.dropdown-menu li.dropdown-item > a,
|
||||||
.dropdown-menu li.dropdown-item > a:hover,
|
.dropdown-menu li.dropdown-item > a:hover,
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
||||||
|
},
|
||||||
|
}
|
186
ui/yarn.lock
186
ui/yarn.lock
|
@ -140,9 +140,9 @@ acorn@^3.0.0, acorn@^3.0.4, acorn@^3.1.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
|
||||||
|
|
||||||
acorn@^4.0.1:
|
acorn@^5.1.1:
|
||||||
version "4.0.4"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
|
||||||
|
|
||||||
add-px-to-style@1.0.0:
|
add-px-to-style@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -200,10 +200,18 @@ ansi-html@0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.6.tgz#bda8e33dd2ee1c20f54c08eb405713cbfc0ed80e"
|
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:
|
ansi-regex@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107"
|
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:
|
ansi-styles@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
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-runtime "^6.0.0"
|
||||||
babel-types "^6.18.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"
|
version "6.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.20.0.tgz#de4a371006139e20990aac0be367d398331204e7"
|
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.20.0.tgz#de4a371006139e20990aac0be367d398331204e7"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1452,7 +1460,7 @@ block-stream@*:
|
||||||
dependencies:
|
dependencies:
|
||||||
inherits "~2.0.0"
|
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"
|
version "3.4.7"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
|
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"
|
deep-eql "^0.1.3"
|
||||||
type-detect "^1.0.0"
|
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:
|
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
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.reject "^4.4.0"
|
||||||
lodash.some "^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"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1832,6 +1850,10 @@ commander@2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
|
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:
|
commander@2.8.x:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
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"
|
readable-stream "~2.0.0"
|
||||||
typedarray "~0.0.5"
|
typedarray "~0.0.5"
|
||||||
|
|
||||||
concat-stream@^1.4.6:
|
concat-stream@^1.5.2:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
|
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1925,6 +1947,19 @@ concat-stream@^1.4.6:
|
||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
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:
|
configstore@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
|
resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
|
||||||
|
@ -2256,6 +2291,10 @@ dashdash@^1.12.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
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:
|
date-now@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
|
@ -2276,11 +2315,11 @@ debug@2.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "0.7.2"
|
ms "0.7.2"
|
||||||
|
|
||||||
debug@^2.1.1, debug@^2.2.0:
|
debug@^2.1.1, debug@^2.2.0, debug@^2.6.3:
|
||||||
version "2.5.2"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.5.2.tgz#50c295a53dbf1657146e0c1b21307275e90d49cb"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "0.7.2"
|
ms "2.0.0"
|
||||||
|
|
||||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
@ -2706,7 +2745,7 @@ escape-string-regexp@1.0.2, escape-string-regexp@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1"
|
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"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
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"
|
doctrine "^1.2.2"
|
||||||
jsx-ast-utils "^1.3.3"
|
jsx-ast-utils "^1.3.3"
|
||||||
|
|
||||||
eslint@3.9.1:
|
eslint-watch@^3.1.2:
|
||||||
version "3.9.1"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.9.1.tgz#5a8597706fc6048bc6061ac754d4a211d28f4f5b"
|
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:
|
dependencies:
|
||||||
babel-code-frame "^6.16.0"
|
babel-code-frame "^6.16.0"
|
||||||
chalk "^1.1.3"
|
chalk "^1.1.3"
|
||||||
concat-stream "^1.4.6"
|
concat-stream "^1.5.2"
|
||||||
debug "^2.1.1"
|
debug "^2.1.1"
|
||||||
doctrine "^1.2.2"
|
doctrine "^2.0.0"
|
||||||
escope "^3.6.0"
|
escope "^3.6.0"
|
||||||
espree "^3.3.1"
|
espree "^3.4.0"
|
||||||
|
esquery "^1.0.0"
|
||||||
estraverse "^4.2.0"
|
estraverse "^4.2.0"
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
file-entry-cache "^2.0.0"
|
file-entry-cache "^2.0.0"
|
||||||
glob "^7.0.3"
|
glob "^7.0.3"
|
||||||
globals "^9.2.0"
|
globals "^9.14.0"
|
||||||
ignore "^3.1.5"
|
ignore "^3.2.0"
|
||||||
imurmurhash "^0.1.4"
|
imurmurhash "^0.1.4"
|
||||||
inquirer "^0.12.0"
|
inquirer "^0.12.0"
|
||||||
is-my-json-valid "^2.10.0"
|
is-my-json-valid "^2.10.0"
|
||||||
|
@ -2787,16 +2843,16 @@ eslint@3.9.1:
|
||||||
require-uncached "^1.0.2"
|
require-uncached "^1.0.2"
|
||||||
shelljs "^0.7.5"
|
shelljs "^0.7.5"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
strip-json-comments "~1.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
table "^3.7.8"
|
table "^3.7.8"
|
||||||
text-table "~0.2.0"
|
text-table "~0.2.0"
|
||||||
user-home "^2.0.0"
|
user-home "^2.0.0"
|
||||||
|
|
||||||
espree@^3.3.1:
|
espree@^3.4.0:
|
||||||
version "3.3.2"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.0.tgz#98358625bdd055861ea27e2867ea729faf463d8d"
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^4.0.1"
|
acorn "^5.1.1"
|
||||||
acorn-jsx "^3.0.0"
|
acorn-jsx "^3.0.0"
|
||||||
|
|
||||||
esprima-fb@^15001.1.0-dev-harmony-fb:
|
esprima-fb@^15001.1.0-dev-harmony-fb:
|
||||||
|
@ -2815,6 +2871,12 @@ esprima@~3.1.0:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
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:
|
esrecurse@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
|
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
|
||||||
|
@ -2826,7 +2888,7 @@ estraverse@^1.9.1:
|
||||||
version "1.9.3"
|
version "1.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
|
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"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
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"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.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"
|
version "9.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
|
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"
|
is-my-json-valid "^2.12.4"
|
||||||
pinkie-promise "^2.0.0"
|
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:
|
has-ansi@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
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"
|
version "1.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||||
|
|
||||||
ignore@^3.1.5:
|
ignore@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
|
||||||
|
|
||||||
immutable@^3.8.1:
|
immutable@^3.8.1:
|
||||||
version "3.8.1"
|
version "3.8.1"
|
||||||
|
@ -4147,6 +4215,10 @@ keycode@^2.1.1:
|
||||||
version "2.1.8"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.8.tgz#94d2b7098215eff0e8f9a8931d5a59076c4532fb"
|
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:
|
kind-of@^3.0.2:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
|
||||||
|
@ -4428,7 +4500,7 @@ lodash.words@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._root "^3.0.0"
|
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"
|
version "4.17.3"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.3.tgz#557ed7d2a9438cac5fd5a43043ca60cb455e01f7"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.3.tgz#557ed7d2a9438cac5fd5a43043ca60cb455e01f7"
|
||||||
|
|
||||||
|
@ -4436,6 +4508,10 @@ lodash@^3.8.0:
|
||||||
version "3.10.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
|
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:
|
lodash@~4.16.4:
|
||||||
version "4.16.6"
|
version "4.16.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
|
||||||
|
@ -4697,6 +4773,10 @@ ms@0.7.2:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
|
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:
|
mustache@^2.2.1:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
|
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"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
|
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:
|
samsam@1.1.2, samsam@~1.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
|
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"
|
source-map-url "~0.3.0"
|
||||||
urix "~0.1.0"
|
urix "~0.1.0"
|
||||||
|
|
||||||
source-map-support@^0.4.2:
|
source-map-support@^0.4.14, source-map-support@^0.4.2:
|
||||||
version "0.4.8"
|
version "0.4.15"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.8.tgz#4871918d8a3af07289182e974e32844327b2e98b"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
|
||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.5.3"
|
source-map "^0.5.6"
|
||||||
|
|
||||||
source-map-url@~0.3.0:
|
source-map-url@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
|
@ -6704,6 +6788,10 @@ spawn-args@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/spawn-args/-/spawn-args-0.2.0.tgz#fb7d0bd1d70fd4316bd9e3dec389e65f9d6361bb"
|
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:
|
spdx-correct@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
|
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"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
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:
|
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||||
|
@ -6845,10 +6939,14 @@ strip-indent@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^4.0.1"
|
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"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
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:
|
style-loader@0.13.1, style-loader@^0.13.0:
|
||||||
version "0.13.1"
|
version "0.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.1.tgz#468280efbc0473023cd3a6cd56e33b5a1d7fc3a9"
|
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"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
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:
|
supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
|
||||||
version "3.1.2"
|
version "3.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^1.0.0"
|
has-flag "^1.0.0"
|
||||||
|
|
||||||
|
@ -6983,7 +7081,7 @@ testem@^1.2.1:
|
||||||
tap-parser "^3.0.2"
|
tap-parser "^3.0.2"
|
||||||
xmldom "^0.1.19"
|
xmldom "^0.1.19"
|
||||||
|
|
||||||
text-table@~0.2.0:
|
text-table@^0.2.0, text-table@~0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
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"
|
version "0.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
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:
|
trim-newlines@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
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"
|
version "1.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
|
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:
|
uniq@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||||
|
|
Loading…
Reference in New Issue